diff --git a/CLAUDE.md b/CLAUDE.md index 73593ec7b..032267145 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,8 +17,10 @@ limitations under the License. # Apache Unomi — CLAUDE.md Apache Unomi is a Java/OSGi customer data platform running on Apache Karaf. Multi-module Maven -reactor; most runtime code is packaged as OSGi bundles wired via Blueprint XML -(`OSGI-INF/blueprint/blueprint.xml` in each module), not Spring/CDI. +reactor; runtime code is packaged as OSGi bundles. **New code uses OSGi Declarative Services** +(`@Component`, `@Reference`, `@Activate`). Legacy modules still ship Blueprint XML +(`OSGI-INF/blueprint/blueprint.xml`) during migration — do not add new Blueprint files; see +`CODING_GUIDELINES.md` and `manual/.../writing-plugins.adoc`. ## Dual persistence backends — the #1 thing to know diff --git a/README.md b/README.md index 9c2fca51c..9499890ad 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,16 @@ Branches compile and be stable. These are recommended for users that prefer to work from the source code. Otherwise you can find packaged binaries on the [Apache Unomi website](https://unomi.apache.org). + +Apache Unomi 3.x +-------------- +Unomi 3.0+ requires **Java 17**, **Karaf 4.4**, and **Elasticsearch 9.x**. Unomi 3.1 adds **multi-tenancy**, **OpenSearch 3** support, a cluster-aware **task scheduler**, and persistence-based clustering. + +* Build and run: see `manual/src/main/asciidoc/building-and-deploying.adoc` or `manual/src/main/asciidoc/5-min-quickstart.adoc` +* Docker: see `docker/README.md` +* Multi-tenancy and API keys: see `manual/src/main/asciidoc/multitenancy.adoc` +* Migrations: see `manual/src/main/asciidoc/migrations/migrations.adoc` + Documentation ------------- You can find all the updated documentation, including building and deployment instructions, on the [Apache Unomi diff --git a/api/src/main/java/org/apache/unomi/api/BatchUpdate.java b/api/src/main/java/org/apache/unomi/api/BatchUpdate.java index 8f55a5195..a68068ee3 100644 --- a/api/src/main/java/org/apache/unomi/api/BatchUpdate.java +++ b/api/src/main/java/org/apache/unomi/api/BatchUpdate.java @@ -20,7 +20,10 @@ import org.apache.unomi.api.conditions.Condition; /** - * A representation of an operation to update the value of a property on items matching a specific condition. + * Bulk property update specification executed as a scroll query. + * Selects items with a {@link org.apache.unomi.api.conditions.Condition}, sets one + * property (Apache Commons BeanUtils expression) to a new value, and optionally + * applies a {@link PropertyMergeStrategyType} when merging complex fields. */ public class BatchUpdate { private String propertyName; @@ -31,11 +34,10 @@ public class BatchUpdate { private int scrollBatchSize = 1000; /** - * Retrieves the property name which value needs to be updated. Note that the property name follows the - * Apache Commons BeanUtils expression - * format + * Property to update, as an Apache Commons BeanUtils expression + * (see BeanUtils expression format). * - * @return an Apache Commons BeanUtils expression identifying which property we want to update + * @return property expression to update */ public String getPropertyName() { return propertyName; @@ -51,7 +53,7 @@ public void setPropertyName(String propertyName) { } /** - * Retrieves the new property value. + * New value to assign to the property. * * @return the new property value */ @@ -69,7 +71,7 @@ public void setPropertyValue(Object propertyValue) { } /** - * Retrieves the condition which items we want to update must satisfy. + * Condition that items must match to be updated. * * @return the condition which items we want to update must satisfy */ @@ -87,7 +89,7 @@ public void setCondition(Condition condition) { } /** - * Retrieves the identifier for the {@link PropertyMergeStrategyType} to use during the update if needed. + * {@link PropertyMergeStrategyType} id to use when merging the new value. * * @return the identifier for the {@link PropertyMergeStrategyType} to use during the update if needed */ 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 a80b5689f..302d0b533 100644 --- a/api/src/main/java/org/apache/unomi/api/ClusterNode.java +++ b/api/src/main/java/org/apache/unomi/api/ClusterNode.java @@ -18,13 +18,17 @@ package org.apache.unomi.api; /** - * Information about a cluster node. + * Runtime snapshot of one Apache Unomi node in a cluster. + * Stores health and topology flags such as load averages, addresses, uptime, + * and whether the node is a master (coordination only) or data node (stores + * context data). Cluster services publish these objects so operators can + * inspect cluster state. */ public class ClusterNode extends Item { private static final long serialVersionUID = 1281422346318230514L; - // Item type identifier for cluster nodes. + /** Item type identifier for cluster nodes. */ public static final String ITEM_TYPE = "clusterNode"; private double cpuLoad; @@ -41,7 +45,7 @@ public class ClusterNode extends Item { private ServerInfo serverInfo; /** - * Instantiates a new Cluster node. + * Creates an empty cluster node with item type {@link #ITEM_TYPE}. */ public ClusterNode() { super(); @@ -49,27 +53,27 @@ public ClusterNode() { } /** - * Retrieves the cpu load. + * Current CPU load on this node. * - * @return the cpu load + * @return CPU load */ public double getCpuLoad() { return cpuLoad; } /** - * Sets the cpu load. + * Sets the CPU load. * - * @param cpuLoad the cpu load + * @param cpuLoad CPU load */ public void setCpuLoad(double cpuLoad) { this.cpuLoad = cpuLoad; } /** - * Retrieves the public host address. + * Public host address clients use to reach this node. * - * @return the public host address + * @return public host address */ public String getPublicHostAddress() { return publicHostAddress; @@ -78,151 +82,151 @@ public String getPublicHostAddress() { /** * Sets the public host address. * - * @param publicHostAddress the public host address + * @param publicHostAddress public host address */ public void setPublicHostAddress(String publicHostAddress) { this.publicHostAddress = publicHostAddress; } /** - * Retrieves the internal host address which uses the HTTP/HTTPS protocol for communications between clients and the context server. + * Internal HTTP/HTTPS address used for client-to-server communication. * - * @return the internal host address + * @return internal host address */ public String getInternalHostAddress() { return internalHostAddress; } /** - * Sets the internal host address which uses the HTTP/HTTPS protocol for communications between clients and the context server. + * Sets the internal HTTP/HTTPS host address. * - * @param internalHostAddress the internal host address + * @param internalHostAddress internal host address */ public void setInternalHostAddress(String internalHostAddress) { this.internalHostAddress = internalHostAddress; } /** - * Retrieves the load average for the last minute, five minutes and fifteen minutes. + * Load averages for the last 1, 5, and 15 minutes. * - * @return an array of {@code double} containing, in order and starting from index {@code 0}, the load average for the last minute, last five minutes and last fifteen minutes + * @return three-element array: index 0 = 1 min, 1 = 5 min, 2 = 15 min */ public double[] getLoadAverage() { return loadAverage; } /** - * Sets the load average for the last minute, five minutes and fifteen minutes. + * Sets load averages for the last 1, 5, and 15 minutes. * - * @param loadAverage an array of {@code double} containing, in order and starting from index {@code 0}, the load average for the last minute, last five minutes and last fifteen minutes + * @param loadAverage three-element array: index 0 = 1 min, 1 = 5 min, 2 = 15 min */ public void setLoadAverage(double[] loadAverage) { this.loadAverage = loadAverage; } /** - * Retrieves the uptime. + * Node uptime in milliseconds. * - * @return the uptime + * @return uptime */ public long getUptime() { return uptime; } /** - * Sets the uptime. + * Sets the node uptime. * - * @param uptime the uptime + * @param uptime uptime in milliseconds */ public void setUptime(long uptime) { this.uptime = uptime; } /** - * Determines whether this ClusterNode is a master node, i.e. this node doesn't store any data but is only focused on cluster management operations. + * Whether this node is a master (coordination only, no local context data). * - * @return {@code true} if this node is a master node, {@code false} otherwise + * @return {@code true} if this is a master node */ public boolean isMaster() { return master; } /** - * Specifies whether this ClusterNode is a master node, i.e. this node doesn't store any data but is only focused on cluster management operations.. + * Sets whether this node is a master (coordination only, no local context data). * - * @param master {@code true} if this node is a master node, {@code false} otherwise + * @param master {@code true} for a master node */ public void setMaster(boolean master) { this.master = master; } /** - * Determines whether this ClusterNode locally stores data. + * Whether this node stores context data locally. * - * @return {@code true} if this node locally stores data, {@code false} otherwise + * @return {@code true} if this is a data node */ public boolean isData() { return data; } /** - * Specifies whether this ClusterNode locally stores data. + * Sets whether this node stores context data locally. * - * @param data {@code true} if this node locally stores data, {@code false} otherwise + * @param data {@code true} for a data node */ public void setData(boolean data) { this.data = data; } /** - * Retrieves the node start time in milliseconds. + * When this node started (milliseconds since epoch). * - * @return the start time + * @return start time */ public long getStartTime() { return startTime; } /** - * Sets the node start time in milliseconds. + * Sets the node start time. * - * @param startTime the start time + * @param startTime start time in milliseconds */ public void setStartTime(long startTime) { this.startTime = startTime; } /** - * Retrieves the last heartbeat time in milliseconds. + * When this node last sent a heartbeat (milliseconds since epoch). * - * @return the last heartbeat time + * @return last heartbeat time */ public long getLastHeartbeat() { return lastHeartbeat; } /** - * Sets the last heartbeat time in milliseconds. + * Sets the last heartbeat time. * - * @param lastHeartbeat the last heartbeat time + * @param lastHeartbeat last heartbeat time in milliseconds */ public void setLastHeartbeat(long lastHeartbeat) { this.lastHeartbeat = lastHeartbeat; } /** - * Gets the server information. + * Build and capability details for this node. * - * @return the server information + * @return server information */ public ServerInfo getServerInfo() { return serverInfo; } /** - * Sets the server information. + * Sets the server information for this node. * - * @param serverInfo the server information + * @param serverInfo server information */ public void setServerInfo(ServerInfo serverInfo) { this.serverInfo = serverInfo; diff --git a/api/src/main/java/org/apache/unomi/api/Consent.java b/api/src/main/java/org/apache/unomi/api/Consent.java index 2cb20ab4b..966c9a53d 100644 --- a/api/src/main/java/org/apache/unomi/api/Consent.java +++ b/api/src/main/java/org/apache/unomi/api/Consent.java @@ -38,18 +38,19 @@ public class Consent implements Serializable { private Date revokeDate; /** - * Empty constructor mostly used for JSON (de-) serialization + * Default constructor for JSON (de)serialization. */ public Consent() { } /** - * A constructor to directly build a consent with all it's properties + * Creates a consent with all properties set. + * * @param scope the scope for this consent * @param typeIdentifier the identifier of the type this consent applies to - * @param status the type of status that we are storing for this consent. May be one of @ConsentStatus.DENIED, @ConsentStatus.GRANTED, @ConsentStatus.REVOKED - * @param statusDate the starting date at which this consent was given - * @param revokeDate the date at which this consent will (automatically) revoke + * @param status the consent status ({@link ConsentStatus#DENIED}, {@link ConsentStatus#GRANTED}, or {@link ConsentStatus#REVOKED}) + * @param statusDate the date when this consent was given + * @param revokeDate the date when this consent will be automatically revoked */ public Consent(String scope, String typeIdentifier, ConsentStatus status, Date statusDate, Date revokeDate) { this.scope = scope; @@ -60,12 +61,12 @@ public Consent(String scope, String typeIdentifier, ConsentStatus status, Date s } /** - * A constructor from a map used for example when we use the deserialized data from event - * properties. - * @param consentMap a Map that contains the following key-value pairs : typeIdentifier:String, status:String (must - * be one of GRANTED, DENIED or REVOKED), statusDate:String (ISO8601 date format !), revokeDate:String (ISO8601 date format !) - * @param dateFormat a DateFormat instance to convert the date string to date objects - * @throws ParseException in case one of the dates failed to parse properly + * Creates a consent from a deserialized property map. + * + * @param consentMap map with keys {@code typeIdentifier}, {@code status} (GRANTED, DENIED, or REVOKED), + * {@code statusDate}, and {@code revokeDate} as ISO-8601 strings + * @param dateFormat date format used to parse date strings + * @throws ParseException if a date string cannot be parsed */ public Consent(Map consentMap, DateFormat dateFormat) throws ParseException { if (consentMap.containsKey("scope")) { @@ -93,58 +94,63 @@ public Consent(Map consentMap, DateFormat dateFormat) throws Pars } /** - * Retrieve the scope for this consent - * @return a scope identifier + * Scope this consent applies to. + * + * @return the scope identifier */ public String getScope() { return scope; } /** - * Set the scope for this consent - * @param scope a scope identifier + * Sets the scope for this consent. + * + * @param scope the scope identifier */ public void setScope(String scope) { this.scope = scope; } /** - * Set the type identifier. This must be (no validation is done) a unique identifier for the consent type. These - * are usually externally defined, Apache Unomi has no knowledge of them except for this type identifier. - * @param typeIdentifier a unique String to identify the consent type + * Sets the consent type identifier (externally defined, not validated by Unomi). + * + * @param typeIdentifier the consent type identifier */ public void setTypeIdentifier(String typeIdentifier) { this.typeIdentifier = typeIdentifier; } /** - * Retrieve the consent type identifier for this consent. - * @return a String containing the type identifier + * Consent type identifier. + * + * @return the type identifier */ public String getTypeIdentifier() { return typeIdentifier; } /** - * Retrieves the status for this consent. This is of type @ConsentStatus - * @return the current value for the status. + * Current consent status. + * + * @return the consent status */ public ConsentStatus getStatus() { return status; } /** - * Sets the status for this consent. A Consent status of type REVOKED means that this consent is meant to be destroyed. - * @param status the status to set on this consent + * Sets the consent status. A status of {@link ConsentStatus#REVOKED} means the consent should be removed. + * + * @param status the consent status to set */ public void setStatus(ConsentStatus status) { this.status = status; } /** - * Retrieve the date at which this consent was given. If this date is in the future the consent should not be - * considered valid yet. - * @return a valid date or null if this date was not set. + * Date when this consent was given. Future dates mean the consent is not yet valid. + * + * @return the status date, or {@code null} if unset */ public Date getStatusDate() { return statusDate; @@ -152,34 +158,35 @@ public Date getStatusDate() { /** * Sets the date from which this consent applies. - * @param statusDate a valid Date or null if we set not starting date (immediately valid) + * + * @param statusDate the effective date, or {@code null} for immediate validity */ public void setStatusDate(Date statusDate) { this.statusDate = statusDate; } /** - * Retrieves the end date for this consent. After this date the consent is no longer valid and should be disposed of. - * If this date is not set it means the consent will never expire - * @return a valid Date or null to indicate an unlimited consent + * Expiration date after which this consent is no longer valid. {@code null} means no expiry. + * + * @return the revoke date, or {@code null} if unlimited */ public Date getRevokeDate() { return revokeDate; } /** - * Sets the end date for this consent. After this date the consent is no longer valid and should be disposed of. - * If this date is not set it means the consent will never expire - * @param revokeDate a valid Date or null to indicate an unlimited consent + * Sets the expiration date for this consent. {@code null} means the consent never expires. + * + * @param revokeDate the revoke date, or {@code null} for unlimited validity */ public void setRevokeDate(Date revokeDate) { this.revokeDate = revokeDate; } /** - * Test if the consent is GRANTED right now. - * @return true if the consent is granted using the current date (internally a new Date() is created and the - * {@link Consent#isConsentGrantedAtDate} is called. + * Whether this consent is granted at the current time. + * + * @return {@code true} if the consent is granted now */ @XmlTransient public boolean isConsentGrantedNow() { @@ -187,9 +194,10 @@ public boolean isConsentGrantedNow() { } /** - * Tests if the consent is GRANTED at the specified date - * @param testDate the date against which to test the consent to be granted. - * @return true if the consent is granted at the specified date, false otherwise. + * Whether this consent is granted at the given date. + * + * @param testDate the date to test against + * @return {@code true} if the consent is granted at the specified date */ @XmlTransient public boolean isConsentGrantedAtDate(Date testDate) { @@ -202,14 +210,10 @@ public boolean isConsentGrantedAtDate(Date testDate) { } /** - * This is a utility method to generate a Map based on the contents of the consents. The format of the map is the - * same as the one used in the Map Consent constructor. For dates you must specify a dateFormat that will be used - * to format the dates. This dateFormat should usually support ISO8601 to make integrate with Javascript clients - * easy to integrate. - * @param dateFormat a dateFormat instance such as ISO8601DateFormat to generate the String formats for the statusDate - * and revokeDate map entries. - * @return a Map that contains the following key-value pairs : typeIdentifier:String, status:String (must - * be one of GRANTED, DENIED or REVOKED), statusDate:String (generated by the dateFormat), revokeDate:String (generated by the dateFormat) + * Serializes this consent to a map using the same format as the map constructor. + * + * @param dateFormat date format for {@code statusDate} and {@code revokeDate} entries (ISO-8601 recommended) + * @return map with keys {@code scope}, {@code typeIdentifier}, {@code status}, {@code statusDate}, and {@code revokeDate} */ @XmlTransient public Map toMap(DateFormat dateFormat) { 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 1f5b20d27..3ece0c5c8 100644 --- a/api/src/main/java/org/apache/unomi/api/ConsentStatus.java +++ b/api/src/main/java/org/apache/unomi/api/ConsentStatus.java @@ -17,14 +17,15 @@ package org.apache.unomi.api; /** - * This enum class represents the type of grant a @Consent might have. The revoke grant type is a special one used to - * remove a consent for a profile. + * Allowed grant states for a profile {@link Consent}. + * Each constant tells whether consent was granted, denied, or later revoked. + * Revoked consents are removed from the profile rather than kept as denied. */ public enum ConsentStatus { - // Consent has been granted. + /** Consent was explicitly granted by the profile. */ GRANTED, - // Consent has been denied. + /** Consent was explicitly denied. */ DENIED, - // Consent has been revoked. + /** Consent was revoked and should be removed from the profile. */ 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 090fcce18..6a81561e8 100644 --- a/api/src/main/java/org/apache/unomi/api/ContextRequest.java +++ b/api/src/main/java/org/apache/unomi/api/ContextRequest.java @@ -77,7 +77,7 @@ public class ContextRequest { private String publicApiKey; /** - * Retrieves the source of the context request. + * Source item for this context request. * * @return the source */ @@ -114,7 +114,7 @@ public void setRequireSegments(boolean requireSegments) { } /** - * Retrieves the list of profile properties the context server should return with its context response. + * Profile property names the client wants in the response. * * @return the required profile properties the client requested to be returned with the response * @see ContextResponse#getProfileProperties() @@ -133,7 +133,7 @@ public void setRequiredProfileProperties(List requiredProfileProperties) } /** - * Retrieves the list of session properties the context server should return with its context response. + * Session property names the client wants in the response. * * @return the required session properties the client requested to be returned with the response * @see ContextResponse#getSessionProperties() @@ -152,26 +152,25 @@ public void setRequiredSessionProperties(List requiredSessionProperties) } /** - * Specifies whether the profiles scores should be part of the ContextResponse. - * @return a boolean indicating if the scores should be part of the response. + * Whether profile scores should be included in the response. + * + * @return {@code true} if scores should be returned */ public boolean isRequireScores() { return requireScores; } /** - * Setting this value to true indicates that the profile scores should be included in the response. By default this - * value is false. - * @param requireScores set to true if you want the scores to be part of the context response + * Sets whether profile scores should be included in the response. + * + * @param requireScores {@code true} to include scores in the context response */ public void setRequireScores(boolean requireScores) { this.requireScores = requireScores; } /** - * Retrieves the filters aimed at content personalization that should be evaluated for the given session and/or profile so that the context server can tell the client - * whether the content associated with the filter should be activated for this profile/session. The filter identifier is used in the {@link ContextResponse} with the - * associated evaluation result. + * Content personalization filters to evaluate for the current session and profile. * * @return the filters aimed at content personalization that should be evaluated for the given session and/or profile * @see ProfileService#matchCondition(Condition, Profile, Session) Details on how the filter conditions are evaluated @@ -209,7 +208,7 @@ public void setPersonalizations(List events) { } /** - * Retrieves the profile overrides. + * Temporary profile overrides for impersonation or preview. * * @return the profile overrides */ @@ -246,7 +245,7 @@ public void setProfileOverrides(Profile overrides) { } /** - * Retrieves the session properties overrides. + * Temporary session property overrides for impersonation or preview. * * @return the session properties overrides */ @@ -264,10 +263,9 @@ public void setSessionPropertiesOverrides(Map sessionPropertiesO } /** - * Retrieve the sessionId passed along with the request. All events will be processed with this sessionId as a - * default + * Session identifier applied as the default for all events in this request. * - * @return the identifier for the session + * @return the session identifier */ public String getSessionId() { return sessionId; @@ -284,10 +282,9 @@ public void setSessionId(String sessionId) { } /** - * Retrieve the profileId passed along with the request. All events will be processed with this profileId as a - * default + * Profile identifier applied as the default for all events in this request. * - * @return the identifier for the profile + * @return the profile identifier */ public String getProfileId() { return profileId; @@ -321,7 +318,8 @@ public void setClientId(String clientId) { } /** - * Gets the public API key used for tenant authentication. + * Public API key used for tenant authentication. + * * @return the public API key */ public String getPublicApiKey() { @@ -330,7 +328,8 @@ public String getPublicApiKey() { /** * Sets the public API key used for tenant authentication. - * @param publicApiKey the public API key to set + * + * @param publicApiKey the public API key */ public void setPublicApiKey(String publicApiKey) { this.publicApiKey = publicApiKey; 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 aa6a00c18..b6e57c341 100644 --- a/api/src/main/java/org/apache/unomi/api/ContextResponse.java +++ b/api/src/main/java/org/apache/unomi/api/ContextResponse.java @@ -63,9 +63,9 @@ public class ContextResponse implements Serializable { private TraceNode requestTracing; /** - * Retrieves the profile identifier associated with the profile of the user on behalf of which the client performed the context request. + * Profile identifier for the user on whose behalf the context request was made. * - * @return the profile identifier associated with the profile of the active user + * @return the profile identifier */ public String getProfileId() { return profileId; @@ -81,7 +81,7 @@ public void setProfileId(String profileId) { } /** - * Retrieves the session identifier associated with the processed request. + * Session identifier for the processed request. * * @return the session identifier associated with the processed request * @see Session @@ -100,7 +100,7 @@ public void setSessionId(String sessionId) { } /** - * Retrieves the profile properties that were requested by the client. + * Profile properties requested by the client. * * @return the profile properties that were requested by the client * @see ContextRequest#getRequiredProfileProperties() @@ -119,7 +119,7 @@ public void setProfileProperties(Map profileProperties) { } /** - * Retrieves the session properties that were requested by the client. + * Session properties requested by the client. * * @return the session properties that were requested by the client * @see ContextRequest#getRequiredSessionProperties() @@ -138,7 +138,7 @@ public void setSessionProperties(Map sessionProperties) { } /** - * Retrieves the identifiers of the profile segments associated with the user if they were requested by the client. Note that these segments are evaluated taking potential + * Profile segment identifiers for the user if they were requested by the client. Note that these segments are evaluated taking potential * overrides as requested by the client or as a result of evaluating overridden properties. * * @return the profile segments associated with the user accounting for potential overrides @@ -157,23 +157,25 @@ public void setProfileSegments(Set profileSegments) { } /** - * Retrieve the current scores for the profile if they were requested in the request using the requireScores boolean. - * @return a map that contains the score identifier as the key and the score value as the value + * Profile scores when requested via {@link ContextRequest#isRequireScores()}. + * + * @return map of scoring identifier to score value */ public Map getProfileScores() { return profileScores; } /** - * Stores the scores for the current profile if requested using the requireScores boolean in the request. - * @param profileScores a map that contains the score identifier as the key and the score value as the value + * Sets profile scores for the response. + * + * @param profileScores map of scoring identifier to score value */ public void setProfileScores(Map profileScores) { this.profileScores = profileScores; } /** - * Retrieves the results of the evaluation content filtering definitions and whether individual definitions match with the associated profile (potentially modified by + * Content filtering evaluation results and whether individual definitions match with the associated profile (potentially modified by * overridden values). * * @return a Map associating the filter identifier as key to its evaluation result by the context server @@ -193,9 +195,9 @@ public void setFilteringResults(Map filteringResults) { /** - * Returns the number of events processed in this request. + * Number of events processed in this request. * - * @return the number of events processed in this request + * @return the processed event count */ public int getProcessedEvents() { return processedEvents; @@ -211,8 +213,9 @@ public void setProcessedEvents(int processedEvents) { } /** - * @deprecated personalizations results are more complex since 2.1.0 and they are now available under: getPersonalizationResults() - * @return the personalization results map + * @deprecated Personalization results are more complex since 2.1.0; use {@link #getPersonalizationResults()} instead. + * + * @return the legacy personalization results map */ @Deprecated public Map> getPersonalizations() { @@ -220,8 +223,9 @@ 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 Personalization results are more complex since 2.1.0; use {@link #setPersonalizationResults(Map)} instead. + * + * @param personalizations the legacy personalization results */ @Deprecated public void setPersonalizations(Map> personalizations) { @@ -229,8 +233,9 @@ public void setPersonalizations(Map> personalizations) { } /** - * Get the result of the personalization resolutions done during the context request. - * @return a Map key/value pair (key:personalization id, value:the result that contains the matching content ids and additional information) + * Personalization resolution results from the context request. + * + * @return map of personalization id to resolution result */ public Map getPersonalizationResults() { return personalizationResults; @@ -246,12 +251,11 @@ public void setPersonalizationResults(Map persona } /** - * Retrieves the tracked conditions, if any, associated with the source of the context request that resulted in this ContextResponse. Upon evaluating the incoming request, - * the context server will determine if there are any rules marked with the "trackedCondition" tag and which source condition matches the source of the incoming request and - * return these tracked conditions to the client that can use them to know that the context server can react to events matching the tracked condition and coming from that - * source. This is, in particular, used to implement form mapping (a solution that allows clients to update user profiles based on values provided when a form is submitted). + * Tracked conditions associated with the request source. + *

+ * Rules tagged with {@code trackedCondition} whose source condition matches the incoming + * request source are returned so clients can emit matching events (for example form mapping). * - * TODO: trackedCondition should be a constant, possibly on the Tag class? * * @return the tracked conditions * @see ContextRequest#getSource() @@ -271,43 +275,43 @@ public void setTrackedConditions(Set trackedConditions) { } /** - * Retrieves the current status of anonymous browsing, as set by the privacy service - * @return anonymous browsing status + * Whether anonymous browsing is enabled, as set by the privacy service. + * + * @return {@code true} if anonymous browsing is active */ public boolean isAnonymousBrowsing() { return anonymousBrowsing; } /** - * Set the user anonymous browsing status - * @param anonymousBrowsing new value for anonymousBrowsing + * Sets the anonymous browsing status. + * + * @param anonymousBrowsing {@code true} to enable anonymous browsing */ public void setAnonymousBrowsing(boolean anonymousBrowsing) { this.anonymousBrowsing = anonymousBrowsing; } /** - * Retrieves the map of consents for the current profile. - * @return a Map where the key is the name of the consent identifier, and the value is a consent object that - * contains all the consent data such as whether the consent was granted or deny, the date of granting/denying - * the date at which the consent will be revoked automatically. + * Consent map for the current profile, keyed by consent identifier. + * + * @return map of consent identifier to consent details */ public Map getConsents() { return consents; } /** - * Sets the map of consents for the current profile. - * @param consents a Map where the key is the name of the consent identifier, and the value is a consent object that - * contains all the consent data such as whether the consent was granted or deny, the date of granting/denying - * the date at which the consent will be revoked automatically. + * Sets the consent map for the current profile. + * + * @param consents map of consent identifier to consent details */ public void setConsents(Map consents) { this.consents = consents; } /** - * Returns the request tracing data. + * Request tracing tree for this context evaluation. * * @return the request tracing data */ 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 fab0917e4..1bd6b0b8f 100644 --- a/api/src/main/java/org/apache/unomi/api/CustomItem.java +++ b/api/src/main/java/org/apache/unomi/api/CustomItem.java @@ -21,7 +21,9 @@ import java.util.Map; /** - * A generic extension of Item for context server extensions, properties are stored in a Map. + * Generic {@link Item} extension for plugin-defined entity types. + * Properties live in a flexible map instead of typed fields, which lets + * extensions store custom payloads without adding new Java classes. */ public class CustomItem extends Item { /** @@ -34,13 +36,13 @@ public class CustomItem extends Item { private Map properties = new HashMap(); /** - * Instantiates a new Custom item. + * Default constructor. */ public CustomItem() { } /** - * Instantiates a new Custom item. + * Creates a custom item with the given identifier and item type. * * @param itemId the item id * @param itemType the item type @@ -51,9 +53,9 @@ public CustomItem(String itemId, String itemType) { } /** - * Retrieves this CustomItem's properties. + * Property map for this custom item. * - * @return a Map of the item's properties associating the property name as key to its value. + * @return a map of property names to values */ public Map getProperties() { return properties; diff --git a/api/src/main/java/org/apache/unomi/api/Event.java b/api/src/main/java/org/apache/unomi/api/Event.java index e6a87285c..988e11cda 100644 --- a/api/src/main/java/org/apache/unomi/api/Event.java +++ b/api/src/main/java/org/apache/unomi/api/Event.java @@ -72,13 +72,13 @@ public class Event extends Item implements TimestampedItem { private transient Map attributes; /** - * Instantiates a new Event. + * Default constructor. */ public Event() { } /** - * Instantiates a new Event. + * Creates an event with the given identifier and context fields. * * @param itemId the event item id identifier * @param eventType the event type identifier @@ -95,7 +95,7 @@ public Event(String itemId, String eventType, Session session, Profile profile, } /** - * Instantiates a new Event. + * Creates an event with a generated identifier and the given context fields. * * @param eventType the event type identifier * @param session the session associated with the event @@ -110,7 +110,7 @@ public Event(String eventType, Session session, Profile profile, String scope, I } /** - * Instantiates a new Event. + * Creates an event with a generated identifier, optional properties, and persistence flag. * * @param eventType the event type identifier * @param session the session associated with the event @@ -127,7 +127,7 @@ public Event(String eventType, Session session, Profile profile, String scope, I } /** - * Instantiates a new Event. + * Creates an event with the given identifier, properties, and persistence flag. * * @param itemId the event item id identifier * @param eventType the event type identifier @@ -172,7 +172,7 @@ private void initEvent(String eventType, Session session, Profile profile, Strin } /** - * Retrieves the session identifier if available. + * Session identifier linked to this event, if known. * * @return the session identifier or {@code null} if unavailable */ @@ -181,7 +181,8 @@ public String getSessionId() { } /** - * Set the session id + * Sets the session identifier. + * * @param sessionId the session id */ public void setSessionId(String sessionId) { @@ -189,7 +190,7 @@ public void setSessionId(String sessionId) { } /** - * Retrieves the profile identifier of the Profile associated with this event + * Profile identifier associated with this event. * * @return the profile id */ @@ -207,7 +208,7 @@ public void setProfileId(String profileId) { } /** - * Retrieves the event type. + * Event type identifier (verb of the event sentence). * * @return the event type */ @@ -216,7 +217,8 @@ public String getEventType() { } /** - * Sets the event type + * Sets the event type. + * * @param eventType the event type */ public void setEventType(String eventType) { @@ -224,7 +226,7 @@ public void setEventType(String eventType) { } /** - * Retrieves the event time stamp + * Timestamp when this event occurred. * * @return the event time stamp */ @@ -233,14 +235,16 @@ public Date getTimeStamp() { } /** - * @param timeStamp set the time stamp + * Sets the event timestamp. + * + * @param timeStamp the event timestamp */ public void setTimeStamp(Date timeStamp) { this.timeStamp = timeStamp; } /** - * Retrieves the profile. + * Profile associated with this event (transient, not serialized). * * @return the profile */ @@ -259,7 +263,7 @@ public void setProfile(Profile profile) { } /** - * Retrieves the session. + * Session associated with this event (transient, not serialized). * * @return the session */ @@ -297,7 +301,7 @@ public void setPersistent(boolean persistent) { } /** - * Retrieves the attributes. Attributes are not serializable, and can be used to provide additional contextual objects such as HTTP request or response objects, etc... + * Non-serializable contextual attributes (for example HTTP request or response objects). * * @return the attributes */ @@ -307,7 +311,8 @@ public Map getAttributes() { } /** - * Sets the map of attribues + * Sets the non-serializable attribute map. + * * @param attributes the attributes map */ public void setAttributes(Map attributes) { @@ -328,7 +333,7 @@ public void setProperty(String name, Object value) { } /** - * Retrieves the value of the property identified by the specified name. + * Value of the named event property. * * @param name the name of the property to be retrieved * @return the value of the property identified by the specified name @@ -338,10 +343,10 @@ public Object getProperty(String name) { } /** - * Retrieves the value of the nested property identified by the specified name. + * Value of a nested property, using dot-separated path segments. * - * @param name the name of the property to be retrieved, splited in the nested properties with "." - * @return the value of the property identified by the specified name + * @param name the property path, split on {@code "."} for nested access + * @return the nested value, or {@code null} if any segment is missing */ public Object getNestedProperty(String name) { if (!name.contains(".")) { @@ -362,7 +367,7 @@ public Object getNestedProperty(String name) { } /** - * Retrieves the properties. + * All event properties. * * @return the properties */ @@ -380,23 +385,25 @@ public void setProperties(Map properties) { } /** - * Retrieves the flattened properties - * @return the flattened properties. + * Flattened property map used for indexing and search. + * + * @return the flattened properties */ public Map getFlattenedProperties() { return flattenedProperties; } /** - * Set the flattened properties for current event - * @param flattenedProperties the properties + * Sets the flattened properties map used for indexing and search. + * + * @param flattenedProperties the flattened properties */ public void setFlattenedProperties(Map flattenedProperties) { this.flattenedProperties = flattenedProperties; } /** - * Retrieves the source. + * Source item of this event (subject of the event sentence). * * @return the source */ @@ -414,7 +421,7 @@ public void setSource(Item source) { } /** - * Retrieves the target. + * Target item of this event (object of the event sentence), if any. * * @return the target */ @@ -432,7 +439,7 @@ public void setTarget(Item target) { } /** - * Retrieves the action post executors for this event, if extra actions need to be executed after all Rule-triggered actions have been processed + * Post-executors to run after all rule-triggered actions complete. * * @return the action post executors */ diff --git a/api/src/main/java/org/apache/unomi/api/EventInfo.java b/api/src/main/java/org/apache/unomi/api/EventInfo.java index 543d6d940..f9d6410a5 100644 --- a/api/src/main/java/org/apache/unomi/api/EventInfo.java +++ b/api/src/main/java/org/apache/unomi/api/EventInfo.java @@ -18,28 +18,53 @@ package org.apache.unomi.api; /** - * Basic event information + * One event type entry in {@link ServerInfo#getEventTypes()}. + * Pairs the event type name with how many matching events exist on the server, + * giving operators and clients a quick view of which event types are active. */ public class EventInfo { private String name; private Long occurences; + /** + * Creates an empty event info record. + */ public EventInfo() { } + /** + * Event type name. + * + * @return event type name + */ public String getName() { return name; } + /** + * Sets the event type name. + * + * @param name event type name + */ public void setName(String name) { this.name = name; } + /** + * Number of occurrences for this event type. + * + * @return occurrence count + */ public Long getOccurences() { return occurences; } + /** + * Sets the occurrence count for this event type. + * + * @param occurences occurrence count + */ public void setOccurences(Long occurences) { this.occurences = occurences; } diff --git a/api/src/main/java/org/apache/unomi/api/EventProperty.java b/api/src/main/java/org/apache/unomi/api/EventProperty.java index d727e1498..aa43d71dc 100644 --- a/api/src/main/java/org/apache/unomi/api/EventProperty.java +++ b/api/src/main/java/org/apache/unomi/api/EventProperty.java @@ -19,9 +19,9 @@ import java.io.Serializable; /** - * An event property. - * - * @author Sergiy Shyrkov + * Named property attached to an {@link Event}. + * Events carry many properties; this type represents one key/value pair + * stored with the event for segmentation, personalization, and reporting. */ public class EventProperty implements Serializable { @@ -32,26 +32,26 @@ public class EventProperty implements Serializable { private String valueType = "string"; /** - * Initializes an instance of this class. + * Default constructor. */ public EventProperty() { super(); } /** - * Initializes an instance of an event property with the string value type. + * Creates an event property with the default string value type. * - * @param id the event property id + * @param id the property identifier */ public EventProperty(String id) { this(id, null); } /** - * Initializes an instance of this class. + * Creates an event property with the given identifier and value type. * - * @param id the event property id - * @param type the type of the value for this property + * @param id the property identifier + * @param type the value type for this property */ public EventProperty(String id, String type) { this(); @@ -62,27 +62,27 @@ public EventProperty(String id, String type) { } /** - * Retrieves the identifier for this EventProperty. + * Property identifier. * - * @return the identifier for this EventProperty + * @return the property id */ public String getId() { return id; } /** - * Sets the identifier. + * Sets the property identifier. * - * @param id the id + * @param id the property id */ public void setId(String id) { this.id = id; } /** - * Retrieves the type. + * Value type for this property (for example {@code string} or {@code integer}). * - * @return the value type + * @return the value type id */ public String getValueType() { return valueType; @@ -91,7 +91,7 @@ public String getValueType() { /** * Sets the value type. * - * @param type the type + * @param type the value type id */ public void setValueType(String type) { this.valueType = type; diff --git a/api/src/main/java/org/apache/unomi/api/EventSource.java b/api/src/main/java/org/apache/unomi/api/EventSource.java index 90128cf6c..df64dd14e 100644 --- a/api/src/main/java/org/apache/unomi/api/EventSource.java +++ b/api/src/main/java/org/apache/unomi/api/EventSource.java @@ -18,7 +18,10 @@ package org.apache.unomi.api; /** - * TODO: REMOVE + * Origin of an {@link Event} in the visitor journey. + * Captures scope, item id, path, and type for the page or asset that produced + * the event. Client trackers populate this object when posting to the events + * collector so rules and analytics can attribute actions to a source context. */ public class EventSource { private String scope; @@ -26,37 +29,80 @@ public class EventSource { private String path; private String type; + /** + * Creates an empty event source. + */ public EventSource() { } + /** + * Scope of the page or asset that originated the event. + * + * @return scope name + */ public String getScope() { return scope; } + /** + * Sets the source scope. + * + * @param scope scope name + */ public void setScope(String scope) { this.scope = scope; } + /** + * Identifier of the originating page or asset within the scope. + * + * @return source item id + */ public String getId() { return id; } + /** + * Sets the source item id. + * + * @param id source item id + */ public void setId(String id) { this.id = id; } + /** + * URL or logical path of the page where the event was triggered. + * + * @return source path + */ public String getPath() { return path; } + /** + * Sets the source path. + * + * @param path source path + */ public void setPath(String path) { this.path = path; } + /** + * Type label for the source object (for example page, form, or product). + * + * @return source type + */ public String getType() { return type; } + /** + * Sets the source type label. + * + * @param type source type + */ public void setType(String type) { this.type = type; } diff --git a/api/src/main/java/org/apache/unomi/api/EventTarget.java b/api/src/main/java/org/apache/unomi/api/EventTarget.java index f1bc1addc..2a5c68d31 100644 --- a/api/src/main/java/org/apache/unomi/api/EventTarget.java +++ b/api/src/main/java/org/apache/unomi/api/EventTarget.java @@ -21,7 +21,10 @@ import java.util.Map; /** - * TODO: REMOVE + * Subject of an {@link Event} — what the visitor interacted with. + * Stores the target item id and type plus optional properties (for example the + * product or content element clicked). Together with {@link EventSource} it + * gives rules enough context to personalize, segment, and report on behavior. */ public class EventTarget implements Serializable { private static final long serialVersionUID = 6370790894348364803L; @@ -29,34 +32,73 @@ public class EventTarget implements Serializable { private String type; private Map properties; + /** + * Default constructor. + */ public EventTarget() { } + /** + * Creates an event target with the given identifier and type. + * + * @param id the unique identifier of the event target + * @param type the type associated with this event target + */ public EventTarget(String id, String type) { this.id = id; this.type = type; } + /** + * Unique identifier of this event target. + * + * @return the event target id + */ public String getId() { return id; } + /** + * Sets the unique identifier of this event target. + * + * @param id the event target id + */ public void setId(String id) { this.id = id; } + /** + * Type label for this event target. + * + * @return the event target type + */ public String getType() { return type; } + /** + * Sets the type label for this event target. + * + * @param type the event target type + */ public void setType(String type) { this.type = type; } + /** + * Arbitrary properties associated with this event target. + * + * @return the property map + */ public Map getProperties() { return properties; } + /** + * Sets the arbitrary properties for this event target. + * + * @param properties the property map + */ public void setProperties(Map properties) { this.properties = properties; } diff --git a/api/src/main/java/org/apache/unomi/api/EventsCollectorRequest.java b/api/src/main/java/org/apache/unomi/api/EventsCollectorRequest.java index 759a71ca8..49ff69965 100644 --- a/api/src/main/java/org/apache/unomi/api/EventsCollectorRequest.java +++ b/api/src/main/java/org/apache/unomi/api/EventsCollectorRequest.java @@ -20,7 +20,10 @@ import java.util.List; /** - * A request for events to be processed. + * JSON body accepted by the events collector REST endpoint. + * Bundles one or more {@link Event} instances plus optional {@code sessionId} + * and {@code profileId} hints so Unomi can attach incoming events to the + * correct session and profile before rules run and persistence writes occur. */ public class EventsCollectorRequest { @@ -36,60 +39,64 @@ public class EventsCollectorRequest { private String publicApiKey; /** - * Retrieves the events to be processed. + * Events submitted for collection and rule evaluation. * - * @return the events to be processed + * @return the event list */ public List getEvents() { return events; } + /** + * Sets the events to collect and process. + * + * @param events the events to submit + */ public void setEvents(List events) { this.events = events; } /** - * Retrieve the sessionId passed along with the request. All events will be processed with this sessionId as a - * default + * Default session id applied to all events in this request when an event does not specify its own. * - * @return the identifier for the session + * @return the session id, or {@code null} if none was provided */ public String getSessionId() { return sessionId; } /** - * Sets the sessionId in the request. This is the preferred method of passing along a session identifier with the - * request, as passing it along in the URL can lead to potential security vulnerabilities. + * Sets the default session id for events in this request. + * Prefer this over passing the session id in the URL to avoid leaking identifiers in logs or referrers. * - * @param sessionId an unique identifier for the session + * @param sessionId the session id */ public void setSessionId(String sessionId) { this.sessionId = sessionId; } /** - * Retrieve the profileId passed along with the request. All events will be processed with this profileId as a - * default + * Default profile id applied to all events in this request when an event does not specify its own. * - * @return the identifier for the profile + * @return the profile id, or {@code null} if none was provided */ public String getProfileId() { return profileId; } /** - * Sets the profileId in the request. + * Sets the default profile id for events in this request. * - * @param profileId an unique identifier for the profile + * @param profileId the profile id */ public void setProfileId(String profileId) { this.profileId = profileId; } /** - * Gets the public API key used for tenant authentication. - * @return the public API key + * Public API key used for tenant authentication. + * + * @return the public API key, or {@code null} if none was provided */ public String getPublicApiKey() { return publicApiKey; @@ -97,7 +104,8 @@ public String getPublicApiKey() { /** * Sets the public API key used for tenant authentication. - * @param publicApiKey the public API key to set + * + * @param publicApiKey the public API key */ public void setPublicApiKey(String publicApiKey) { this.publicApiKey = publicApiKey; diff --git a/api/src/main/java/org/apache/unomi/api/ExecutionContext.java b/api/src/main/java/org/apache/unomi/api/ExecutionContext.java index 39b13f383..93679f099 100644 --- a/api/src/main/java/org/apache/unomi/api/ExecutionContext.java +++ b/api/src/main/java/org/apache/unomi/api/ExecutionContext.java @@ -21,17 +21,28 @@ import java.util.Stack; /** - * Represents the execution context for operations in Unomi, including security and tenant information. + * Security and tenant context for an in-flight operation. + * Carries the active tenant id, roles, permissions, and helpers to check + * access or temporarily switch tenant scope. Services read this object to + * enforce multi-tenant isolation and authorization. */ public class ExecutionContext { + /** Identifier of the system tenant, used for cross-tenant administration. */ public static final String SYSTEM_TENANT = "system"; - + private String tenantId; private Set roles = new HashSet<>(); private Set permissions = new HashSet<>(); private Stack tenantStack = new Stack<>(); private boolean isSystem = false; - + + /** + * Creates a context for the given tenant, roles, and permissions. + * + * @param tenantId tenant id + * @param roles roles assigned in this context, or {@code null} + * @param permissions explicit permissions granted in this context, or {@code null} + */ public ExecutionContext(String tenantId, Set roles, Set permissions) { this.tenantId = tenantId; if (tenantId != null && tenantId.equals(SYSTEM_TENANT)) { @@ -44,57 +55,112 @@ public ExecutionContext(String tenantId, Set roles, Set permissi this.permissions.addAll(permissions); } } - + + /** + * Returns a context for the system tenant. + * + * @return system execution context + */ public static ExecutionContext systemContext() { ExecutionContext context = new ExecutionContext(SYSTEM_TENANT, null, null); context.isSystem = true; return context; } - + + /** + * Active tenant id. + * + * @return tenant id + */ public String getTenantId() { return tenantId; } - + + /** + * Roles assigned to this context. + * + * @return copy of the role set + */ public Set getRoles() { return new HashSet<>(roles); } - + + /** + * Permissions granted to this context. + * + * @return copy of the permission set + */ public Set getPermissions() { return new HashSet<>(permissions); } - + + /** + * Whether this context represents the system tenant. + * + * @return {@code true} for the system tenant + */ public boolean isSystem() { return isSystem; } - + + /** + * Switches to a new tenant, saving the previous tenant on an internal stack. + * + * @param tenantId new tenant id + */ public void setTenant(String tenantId) { tenantStack.push(this.tenantId); this.tenantId = tenantId; this.isSystem = SYSTEM_TENANT.equals(tenantId); } + /** + * Restores the tenant saved by the most recent {@link #setTenant(String)} call. + */ public void restorePreviousTenant() { if (!tenantStack.isEmpty()) { this.tenantId = tenantStack.pop(); this.isSystem = SYSTEM_TENANT.equals(this.tenantId); } } - + + /** + * Checks that this context may perform the given operation. + * System contexts always pass. Otherwise the operation name must be present + * in the granted permissions. + * + * @param operation operation name to validate + * @throws SecurityException if the permission is missing + */ public void validateAccess(String operation) { if (isSystem) { return; } - + if (!hasPermission(operation)) { throw new SecurityException("Access denied: Missing permission for operation " + operation + " for tenant " + tenantId + " and roles " + roles); } } - + + /** + * Whether this context has the given permission. + * System contexts always return {@code true}. + * + * @param permission permission name + * @return {@code true} if the permission is granted + */ public boolean hasPermission(String permission) { return isSystem || permissions.contains(permission); } - + + /** + * Whether this context has the given role. + * System contexts always return {@code true}. + * + * @param role role name + * @return {@code true} if the role is granted + */ public boolean hasRole(String role) { return isSystem || roles.contains(role); } -} \ No newline at end of file +} \ No newline at end of file diff --git a/api/src/main/java/org/apache/unomi/api/GeoPoint.java b/api/src/main/java/org/apache/unomi/api/GeoPoint.java index b3d0d02a6..77a1ebe97 100644 --- a/api/src/main/java/org/apache/unomi/api/GeoPoint.java +++ b/api/src/main/java/org/apache/unomi/api/GeoPoint.java @@ -20,7 +20,9 @@ import java.util.Map; /** - * GeoPoint represents a point in geographical coordinate system using latitude and longitude. + * A geographic location expressed as latitude and longitude. + * Used on profiles, events, and queries when rules or segments need to + * match users or actions by location. */ public class GeoPoint { @@ -35,7 +37,7 @@ public class GeoPoint { private Double lon; /** - * Instantiates a new GeoPoint + * Creates a geo point at the given coordinates. * * @param lat latitude of the geo point * @param lon longitude of the geo point @@ -46,7 +48,7 @@ public GeoPoint(Double lat, Double lon) { } /** - * Retrieves latitude of the geo point + * Latitude of this geo point. * * @return geo point latitude */ @@ -55,7 +57,7 @@ public Double getLat() { } /** - * Retrieves longitude of the geo point + * Longitude of this geo point. * * @return geo point longitude */ @@ -98,11 +100,11 @@ public double distanceTo(final GeoPoint other) { } /** - * Instantiates geo point from map of coordinates + * Parses a geo point from a coordinate map with {@code lat} and {@code lon} keys. * - * @param map Map containing coordinates with keys "lat" and "lon" - * @return New geo point or null if map is not a valid geo point - * @throws IllegalArgumentException Thrown if the input is not valid + * @param map map containing coordinates with keys {@code lat} and {@code lon} + * @return a new geo point + * @throws IllegalArgumentException if the map is null, empty, or missing required keys */ public static GeoPoint fromMap(Map map) { if (map == null || map.isEmpty() || !map.containsKey("lat") || !map.containsKey("lon")) { @@ -112,11 +114,11 @@ public static GeoPoint fromMap(Map map) { } /** - * Instantiates geo point from string representation + * Parses a geo point from a {@code "lat, lon"} string. * - * @param input String geo point representation in the following format: "lat, lon" - * @return New geo point or null if string is not a valid geo point - * @throws IllegalArgumentException Thrown if the input is not valid + * @param input string geo point representation in the format {@code "lat, lon"} + * @return a new geo point, or {@code null} if the input is blank + * @throws IllegalArgumentException if the input cannot be parsed */ public static GeoPoint fromString(final String input) { if (input == null || input.trim().length() == 0) { 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 ec796de52..b8e63d4f2 100644 --- a/api/src/main/java/org/apache/unomi/api/Item.java +++ b/api/src/main/java/org/apache/unomi/api/Item.java @@ -50,6 +50,13 @@ public abstract class Item implements Serializable, YamlConvertible { private static final Map itemTypeCache = new ConcurrentHashMap<>(); + /** + * Resolves the item type string from a class's {@code ITEM_TYPE} constant. + * Results are cached per class. + * + * @param clazz item class + * @return item type, or {@code null} if {@code ITEM_TYPE} is missing or inaccessible + */ public static String getItemType(Class clazz) { String itemType = itemTypeCache.get(clazz); if (itemType != null) { @@ -67,10 +74,29 @@ public static String getItemType(Class clazz) { return null; } + /** + * Unique id used when this item is persisted or referenced. + * Must be unique among items of the same {@link #itemType}. + */ protected String itemId; + /** + * Persistence type string for this item. + * Subclasses must define a public {@code ITEM_TYPE} constant with this value. + */ protected String itemType; + /** + * Scope that groups related items (often one analyzed site). + * Used by clients to filter data returned from the context server. + */ protected String scope; + /** + * Optimistic-locking version, incremented when the item is updated. + */ protected Long version; + /** + * Server-managed metadata keyed by string. + * Stores values that are not part of the item's core properties. + */ protected Map systemMetadata = new HashMap<>(); private String tenantId; @@ -82,6 +108,11 @@ public static String getItemType(Class clazz) { private String sourceInstanceId; private Date lastSyncDate; + /** + * Initializes {@link #itemType} from the subclass {@code ITEM_TYPE} constant + * and sets default audit metadata ({@link #creationDate}, {@link #version}). + * Logs an error when {@code ITEM_TYPE} is missing on the concrete class. + */ public Item() { this.itemType = getItemType(this.getClass()); if (itemType == null) { @@ -90,6 +121,11 @@ public Item() { initializeAuditMetadata(); } + /** + * Creates an item with the given id. + * + * @param itemId item id + */ public Item(String itemId) { this(); this.itemId = itemId; @@ -102,50 +138,56 @@ private void initializeAuditMetadata() { } /** - * Retrieves the Item's identifier used to uniquely identify this Item when persisted or when referred to. An Item's identifier must be unique among Items with the same type. + * Unique id among items of the same type. + * No particular format is required as long as the id is unique for this item type. * - * @return a String representation of the identifier, no particular format is prescribed as long as it is guaranteed unique for this particular Item. + * @return item id */ public String getItemId() { return itemId; } + /** + * Sets the item id. + * + * @param itemId item id + */ public void setItemId(String itemId) { this.itemId = itemId; } /** - * Retrieves the Item's type used to assert metadata and structure common to Items of this type, notably for persistence purposes. The Item's type must - * match the value defined by the implementation's {@code ITEM_TYPE} public constant. + * Item type used for persistence and metadata. + * Must match the implementing class {@code ITEM_TYPE} constant. * - * @return a String representation of this Item's type, must equal the {@code ITEM_TYPE} value + * @return item type */ public String getItemType() { return itemType; } /** - * Sets the Item's type. + * Sets the item type. * - * @param itemType the Item's type + * @param itemType item type */ public void setItemType(String itemType) { this.itemType = itemType; } /** - * Retrieves the Item's scope. + * Scope that groups related items. * - * @return the Item's scope name + * @return scope name */ public String getScope() { return scope; } /** - * Sets the Item's scope. + * Sets the scope. * - * @param scope the Item's scope + * @param scope scope name */ public void setScope(String scope) { this.scope = scope; @@ -166,152 +208,176 @@ public int hashCode() { return itemId != null ? itemId.hashCode() : 0; } + /** + * Optimistic-locking version. + * + * @return item version + */ public Long getVersion() { return version; } + /** + * Sets the item version. + * + * @param version item version + */ public void setVersion(Long version) { this.version = version; } /** - * Returns the system metadata for the given key. + * Returns system metadata for the given key. * - * @param key the key - * @return the system metadata for the given key + * @param key metadata key + * @return metadata value */ public Object getSystemMetadata(String key) { return systemMetadata.get(key); } /** - * Sets the system metadata for the given key. + * Sets system metadata for the given key. * - * @param key the key - * @param value the value + * @param key metadata key + * @param value metadata value */ public void setSystemMetadata(String key, Object value) { systemMetadata.put(key, value); } + /** + * Tenant that owns this item. + * + * @return tenant id + */ public String getTenantId() { return tenantId; } /** - * Sets the tenant ID. + * Sets the tenant id. * - * @param tenantId the tenant ID + * @param tenantId tenant id */ public void setTenantId(String tenantId) { this.tenantId = tenantId; } - // Audit metadata getters and setters + /** + * User or system that created this item. + * + * @return creator id + */ public String getCreatedBy() { return createdBy; } /** - * Sets the created by. + * Sets the creator id. * - * @param createdBy the created by + * @param createdBy creator id */ public void setCreatedBy(String createdBy) { this.createdBy = createdBy; } + /** + * User or system that last modified this item. + * + * @return last modifier id + */ public String getLastModifiedBy() { return lastModifiedBy; } /** - * Sets the last modified by. + * Sets the last modifier id. * - * @param lastModifiedBy the last modified by + * @param lastModifiedBy last modifier id */ public void setLastModifiedBy(String lastModifiedBy) { this.lastModifiedBy = lastModifiedBy; } /** - * Returns the date when this item was created. + * When this item was created. * - * @return the date when this item was created + * @return creation date */ public Date getCreationDate() { return creationDate; } /** - * Sets the date when this item was created. + * Sets the creation date. * - * @param creationDate the creation date + * @param creationDate creation date */ public void setCreationDate(Date creationDate) { this.creationDate = creationDate; } /** - * Returns the date when this item was last modified. + * When this item was last modified. * - * @return the date when this item was last modified + * @return last modification date */ public Date getLastModificationDate() { return lastModificationDate; } /** - * Sets the date when this item was last modified. + * Sets the last modification date. * - * @param lastModificationDate the last modification date + * @param lastModificationDate last modification date */ public void setLastModificationDate(Date lastModificationDate) { this.lastModificationDate = lastModificationDate; } /** - * Returns the source instance ID. + * Cluster node that originated this item. * - * @return the source instance ID + * @return source instance id */ public String getSourceInstanceId() { return sourceInstanceId; } /** - * Sets the source instance ID. + * Sets the source instance id. * - * @param sourceInstanceId the source instance ID + * @param sourceInstanceId source instance id */ public void setSourceInstanceId(String sourceInstanceId) { this.sourceInstanceId = sourceInstanceId; } /** - * Returns the last synchronization date. + * When this item was last synchronized from another node. * - * @return the last synchronization date + * @return last sync date */ public Date getLastSyncDate() { return lastSyncDate; } /** - * Sets the last synchronization date. + * Sets the last sync date. * - * @param lastSyncDate the last synchronization date + * @param lastSyncDate last sync date */ public void setLastSyncDate(Date lastSyncDate) { this.lastSyncDate = lastSyncDate; } /** - * Converts this item to a Map structure for YAML output. - * Implements YamlConvertible interface with circular reference detection. + * Converts this item to a map for YAML output. * - * @param visited set of already visited objects to prevent infinite recursion (may be null) - * @return a Map representation of this item + * @param visited objects already visited while converting (may be {@code null}) + * @param maxDepth remaining recursion depth + * @return map representation of this item */ @Override public Map toYaml(Set visited, int maxDepth) { diff --git a/api/src/main/java/org/apache/unomi/api/Metadata.java b/api/src/main/java/org/apache/unomi/api/Metadata.java index 4f650589f..ec643e2d8 100644 --- a/api/src/main/java/org/apache/unomi/api/Metadata.java +++ b/api/src/main/java/org/apache/unomi/api/Metadata.java @@ -29,7 +29,9 @@ import static org.apache.unomi.api.utils.YamlUtils.circularRef; /** - * A class providing information about context server entities. + * Common descriptive fields shared by Unomi configuration items. + * Includes id, name, description, scope, tags, and related attributes that + * appear on segments, rules, property types, and other managed entities. * * @see MetadataItem */ @@ -53,27 +55,27 @@ public class Metadata implements Comparable, Serializable, YamlConvert private boolean readOnly = false; /** - * Instantiates a new Metadata. + * Default constructor. */ public Metadata() { } /** - * Instantiates a new Metadata with the specified identifier. + * Creates metadata with the given identifier. * - * @param id the identifier for this Metadata + * @param id the metadata identifier */ public Metadata(String id) { this.id = id; } /** - * Instantiates a new Metadata with the provided information. + * Creates metadata with scope, identifier, name, and description. * - * @param scope the scope for this Metadata - * @param id the identifier of the associated {@link Item} - * @param name the name - * @param description the description + * @param scope the item scope + * @param id the item identifier + * @param name the display name + * @param description the human-readable description */ public Metadata(String scope, String id, String name, String description) { this.scope = scope; @@ -83,25 +85,25 @@ public Metadata(String scope, String id, String name, String description) { } /** - * Retrieves the identifier for the entity associated with this Metadata + * Identifier of the item described by this metadata. * - * @return the identifier + * @return the item id */ public String getId() { return id; } /** - * Sets the id. + * Sets the item identifier. * - * @param id the id + * @param id the item id */ public void setId(String id) { this.id = id; } /** - * Retrieves the name. + * Display name shown in administrative UIs. * * @return the name */ @@ -110,7 +112,7 @@ public String getName() { } /** - * Sets the name. + * Sets the display name. * * @param name the name */ @@ -119,7 +121,7 @@ public void setName(String name) { } /** - * Retrieves the description. + * Human-readable description of the item. * * @return the description */ @@ -137,9 +139,9 @@ public void setDescription(String description) { } /** - * Retrieves the scope for the entity associated with this Metadata + * Scope that owns this item. * - * @return the scope for the entity associated with this Metadata + * @return the scope id * @see Item Item for a deeper discussion of scopes */ public String getScope() { @@ -147,36 +149,36 @@ public String getScope() { } /** - * Sets the scope. + * Sets the item scope. * - * @param scope the scope + * @param scope the scope id */ public void setScope(String scope) { this.scope = scope; } /** - * Retrieves a set of {@link String} tag names associated with this Metadata + * User-defined tags for organizing and filtering items. * - * @return a set of {@link String} tag names associated with this Metadata + * @return the tag names */ public Set getTags() { return tags; } /** - * Sets the tags. + * Sets the user-defined tags. * - * @param tags the tag ids + * @param tags the tag names */ public void setTags(Set tags) { this.tags = tags; } /** - * Retrieves a set of {@link String} system tag names associated with this Metadata + * System tags applied by the platform (not editable in UIs). * - * @return a set of {@link String} system tag names associated with this Metadata + * @return the system tag names */ public Set getSystemTags() { return systemTags; @@ -185,32 +187,32 @@ public Set getSystemTags() { /** * Sets the system tags. * - * @param systemTags the system tag ids + * @param systemTags the system tag names */ public void setSystemTags(Set systemTags) { this.systemTags = systemTags; } /** - * Whether the associated entity is considered active by the context server, in particular to check if rules need to be created / triggered + * Whether the associated item is active and eligible for rule evaluation. * - * @return {@code true} if the associated entity is enabled, {@code false} otherwise + * @return {@code true} if enabled, {@code false} otherwise */ public boolean isEnabled() { return enabled; } /** - * Specifies whether the associated entity should be active or not. + * Sets whether the associated item is active. * - * @param enabled {@code true} if the associated entity is enabled, {@code false} otherwise + * @param enabled {@code true} to enable the item, {@code false} to disable it */ public void setEnabled(boolean enabled) { this.enabled = enabled; } /** - * Whether the associated entity is waiting on additional plugins to become available to be able to properly perform its function. + * Whether required OSGi plugins are missing and the item cannot run yet. * * @return {@code true} if plugins are missing, {@code false} otherwise */ @@ -219,7 +221,7 @@ public boolean isMissingPlugins() { } /** - * Specifies whether the associated entity is waiting on additional plugins to become available. + * Sets whether required plugins are missing. * * @param missingPlugins {@code true} if plugins are missing, {@code false} otherwise */ @@ -228,41 +230,47 @@ public void setMissingPlugins(boolean missingPlugins) { } /** - * Whether the associated entity is considered for internal purposes only and should therefore be hidden to accessing UIs. + * Whether the item should be hidden from administrative UIs. * - * @return {@code true} if the associated entity needs to be hidden, {@code false} otherwise + * @return {@code true} if hidden, {@code false} otherwise */ public boolean isHidden() { return hidden; } /** - * Specifies whether the associated entity is hidden. + * Sets whether the item is hidden from UIs. * - * @param hidden {@code true} if the associated entity needs to be hidden, {@code false} otherwise + * @param hidden {@code true} to hide the item, {@code false} to show it */ public void setHidden(boolean hidden) { this.hidden = hidden; } /** - * Whether the associated entity can be accessed but not modified. + * Whether the item can be read but not modified. * - * @return {@code true} if the associated entity can be accessed but not modified, {@code false} otherwise + * @return {@code true} if read-only, {@code false} otherwise */ public boolean isReadOnly() { return readOnly; } /** - * Specifies whether the associated entity should be only accessed and not modified. + * Sets whether the item is read-only. * - * @param readOnly {@code true} if the associated entity can be accessed but not modified, {@code false} otherwise + * @param readOnly {@code true} for read-only access, {@code false} to allow updates */ public void setReadOnly(boolean readOnly) { this.readOnly = readOnly; } + /** + * Compares metadata by item identifier. + * + * @param o the other metadata + * @return a negative, zero, or positive value depending on id ordering + */ public int compareTo(Metadata o) { return getId().compareTo(o.getId()); } diff --git a/api/src/main/java/org/apache/unomi/api/MetadataItem.java b/api/src/main/java/org/apache/unomi/api/MetadataItem.java index 68cf3b324..53ffc91ad 100644 --- a/api/src/main/java/org/apache/unomi/api/MetadataItem.java +++ b/api/src/main/java/org/apache/unomi/api/MetadataItem.java @@ -28,30 +28,46 @@ import static org.apache.unomi.api.utils.YamlUtils.toYamlValue; /** - * A superclass for all {@link Item}s that bear {@link Metadata}. + * Base {@link Item} for entities that carry {@link Metadata}. + * Most user-facing definitions (segments, rules, property types, etc.) + * extend this class so they share the same identification and tagging model. */ public abstract class MetadataItem extends Item { private static final long serialVersionUID = -2459510107927663510L; + /** The associated {@link Metadata} object for this item. */ protected Metadata metadata; + /** + * Default constructor. + */ public MetadataItem() { } + /** + * Creates an item with the given metadata and sets the item id from it. + * + * @param metadata the item metadata + */ public MetadataItem(Metadata metadata) { super(metadata != null ? metadata.getId() : null); this.metadata = metadata; } /** - * Retrieves the associated Metadata. + * Descriptive metadata for this item. * - * @return the associated Metadata + * @return the metadata */ @XmlElement(name = "metadata") public Metadata getMetadata() { return metadata; } + /** + * Sets the metadata and updates the item id when metadata is non-null. + * + * @param metadata the metadata to assign + */ public void setMetadata(Metadata metadata) { if (metadata != null) { this.itemId = metadata.getId(); @@ -59,6 +75,12 @@ public void setMetadata(Metadata metadata) { this.metadata = metadata; } + /** + * Returns the scope for this item, taken from {@link Metadata} when present. + * Falls back to the item-level scope field otherwise. + * + * @return the scope id, or {@code null} if none is set + */ @XmlTransient public String getScope() { if (metadata != null) { diff --git a/api/src/main/java/org/apache/unomi/api/Parameter.java b/api/src/main/java/org/apache/unomi/api/Parameter.java index 10cd6083d..c34ad37fc 100644 --- a/api/src/main/java/org/apache/unomi/api/Parameter.java +++ b/api/src/main/java/org/apache/unomi/api/Parameter.java @@ -28,8 +28,11 @@ import static org.apache.unomi.api.utils.YamlUtils.toYamlValue; /** - * A representation of a condition parameter, to be used in the segment building UI to either select parameters from a - * choicelist or to enter a specific value. + * Parameter definition for a {@link org.apache.unomi.api.conditions.ConditionType}. + * Describes how the segment builder UI should collect a value: free text, choice + * list, or nested structure. Saved on condition types and validated by + * {@link org.apache.unomi.api.services.ConditionValidationService} when rules + * or segments are stored. */ public class Parameter implements Serializable, YamlConvertible { @@ -44,35 +47,75 @@ public class Parameter implements Serializable, YamlConvertible { private Object defaultValue; private ConditionValidation validation; + /** + * Default constructor. + */ public Parameter() { } + /** + * Creates a parameter with id, type, and multivalued flag. + * + * @param id the parameter identifier + * @param type the parameter data type (for example {@code string} or {@code integer}) + * @param multivalued whether multiple values are allowed + */ public Parameter(String id, String type, boolean multivalued) { this.id = id; this.type = type; this.multivalued = multivalued; } + /** + * Parameter identifier. + * + * @return the parameter id + */ public String getId() { return id; } + /** + * Sets the parameter identifier. + * + * @param id the parameter id + */ public void setId(String id) { this.id = id; } + /** + * Parameter data type (for example {@code string}, {@code integer}, or {@code boolean}). + * + * @return the type id + */ public String getType() { return type; } + /** + * Sets the parameter data type. + * + * @param type the type id + */ public void setType(String type) { this.type = type; } + /** + * Whether this parameter accepts multiple values. + * + * @return {@code true} if multivalued, {@code false} otherwise + */ public boolean isMultivalued() { return multivalued; } + /** + * Sets whether this parameter accepts multiple values. + * + * @param multivalued {@code true} to allow multiple values + */ public void setMultivalued(boolean multivalued) { this.multivalued = multivalued; } @@ -86,18 +129,38 @@ public void setChoiceListInitializerFilter(String choiceListInitializerFilter) { // Avoid errors when deploying old definitions } + /** + * Default value used when a condition does not supply this parameter. + * + * @return the default value, or {@code null} if none is configured + */ public Object getDefaultValue() { return defaultValue; } + /** + * Sets the default value for this parameter. + * + * @param defaultValue the default value + */ public void setDefaultValue(Object defaultValue) { this.defaultValue = defaultValue; } + /** + * Validation rules applied when this parameter is used in a condition. + * + * @return the validation rules, or {@code null} if none are configured + */ public ConditionValidation getValidation() { return validation; } + /** + * Sets validation rules for this parameter. + * + * @param validation the validation rules + */ public void setValidation(ConditionValidation validation) { this.validation = validation; } diff --git a/api/src/main/java/org/apache/unomi/api/PartialList.java b/api/src/main/java/org/apache/unomi/api/PartialList.java index 568cf334e..f6fe43a44 100644 --- a/api/src/main/java/org/apache/unomi/api/PartialList.java +++ b/api/src/main/java/org/apache/unomi/api/PartialList.java @@ -23,10 +23,11 @@ import java.util.List; /** - * A list of elements representing a limited view of a larger list, starting from a given element (offset from the first) and showing only a given number of elements, instead of - * showing all of them. This is useful to retrieve "pages" of large element collections. + * Window into a larger result set for paginated queries. + * Carries the current page, offset, total hit count, and optional scroll + * continuation tokens for deep result sets. * - * @param the generic type of contained elements + * @param the element type */ public class PartialList implements Serializable { @@ -45,12 +46,14 @@ public class PartialList implements Serializable { * relation we can understand if we are in the case of an accurate hit or not. */ public enum Relation { + /** Total size equals the reported value exactly. */ EQUAL, + /** Total size is at least the reported value (approximate lower bound). */ GREATER_THAN_OR_EQUAL_TO } /** - * Instantiates a new PartialList. + * Default constructor with an empty list and zero counts. */ public PartialList() { list = new ArrayList<>(); @@ -61,13 +64,13 @@ public PartialList() { } /** - * Instantiates a new PartialList. + * Creates a partial list view over a full result set. * - * @param list the limited view into the bigger List this PartialList is representing - * @param offset the offset of the first element in the view - * @param pageSize the number of elements this PartialList contains - * @param totalSize the total size of elements in the original List - * @param totalSizeRelation the relation to the total size (equals or greater than) + * @param list the page of elements + * @param offset index of the first element in the full set + * @param pageSize number of elements in this page + * @param totalSize total elements in the full set + * @param totalSizeRelation whether {@code totalSize} is exact or a lower bound */ public PartialList(List list, long offset, long pageSize, long totalSize, Relation totalSizeRelation) { this.list = list; @@ -78,66 +81,81 @@ public PartialList(List list, long offset, long pageSize, long totalSize, Rel } /** - * Retrieves the limited list view. + * Elements in the current page. * - * @return a List of the {@code size} elements starting from the {@code offset}-th one from the original, larger list + * @return the page contents */ public List getList() { return list; } /** - * Sets the view list. + * Sets the page contents. * - * @param list the view list into the bigger List this PartialList is representing + * @param list the elements for this page */ public void setList(List list) { this.list = list; } /** - * Retrieves the offset of the first element of the view. + * Index of the first element in the full result set. * - * @return the offset of the first element of the view + * @return the offset */ public long getOffset() { return offset; } + /** + * Sets the offset of the first element in the full result set. + * + * @param offset the starting index + */ public void setOffset(long offset) { this.offset = offset; } /** - * Retrieves the number of elements this PartialList contains. + * Maximum number of elements requested for this page. * - * @return the number of elements this PartialList contains + * @return the page size */ public long getPageSize() { return pageSize; } + /** + * Sets the page size. + * + * @param pageSize the maximum number of elements per page + */ public void setPageSize(long pageSize) { this.pageSize = pageSize; } /** - * Retrieves the total size of elements in the original List. + * Total number of matching elements in the full result set. * - * @return the total size of elements in the original List + * @return the total size */ public long getTotalSize() { return totalSize; } + /** + * Sets the total number of matching elements. + * + * @param totalSize the total size + */ public void setTotalSize(long totalSize) { this.totalSize = totalSize; } /** - * Retrieves the size of this PartialList. Should equal {@link #getPageSize()}. + * Number of elements in the current page (should match {@link #getPageSize()}). * - * @return the size of this PartialList + * @return the number of elements in {@link #getList()} */ @XmlTransient public int size() { @@ -145,10 +163,10 @@ public int size() { } /** - * Retrieves the element at the specified index + * Element at the given index within the current page. * - * @param index the index of the element to retrieve - * @return the element at the specified index + * @param index the zero-based index in the page list + * @return the element at that index */ @XmlTransient public T get(int index) { @@ -156,38 +174,55 @@ public T get(int index) { } /** - * Retrieve the scroll identifier to make it possible to continue a scrolling list query - * @return a string containing the scroll identifier, to be sent back in an subsequent request + * Scroll token returned by the search backend for the next page of a scroll query. + * + * @return the scroll identifier, or {@code null} if scrolling is not in use */ public String getScrollIdentifier() { return scrollIdentifier; } + /** + * Sets the scroll token for continuing a scroll query. + * + * @param scrollIdentifier the scroll identifier + */ public void setScrollIdentifier(String scrollIdentifier) { this.scrollIdentifier = scrollIdentifier; } /** - * Retrieve the value of the scroll time validity to make it possible to continue a scrolling list query - * @return a string containing a time value for the scroll validity, to be sent back in a subsequent request + * How long the scroll context remains valid (for example {@code 10m}). + * + * @return the scroll time validity, or {@code null} if not set */ public String getScrollTimeValidity() { return scrollTimeValidity; } + /** + * Sets how long the scroll context remains valid. + * + * @param scrollTimeValidity the validity period (for example {@code 10m}) + */ public void setScrollTimeValidity(String scrollTimeValidity) { this.scrollTimeValidity = scrollTimeValidity; } /** - * Retrieve the relation to the total site, wether it is equal to or greater than the value stored in the - * totalSize property. - * @return a Relation enum value that describes the type of total size we have in this object. + * Whether {@link #getTotalSize()} is exact or only a lower bound. + * + * @return the total-size relation */ public Relation getTotalSizeRelation() { return totalSizeRelation; } + /** + * Sets whether the reported total size is exact or a lower bound. + * + * @param totalSizeRelation the total-size relation + */ public void setTotalSizeRelation(Relation totalSizeRelation) { this.totalSizeRelation = totalSizeRelation; } diff --git a/api/src/main/java/org/apache/unomi/api/Patch.java b/api/src/main/java/org/apache/unomi/api/Patch.java index b7fb457b4..1290452cf 100644 --- a/api/src/main/java/org/apache/unomi/api/Patch.java +++ b/api/src/main/java/org/apache/unomi/api/Patch.java @@ -29,9 +29,20 @@ import java.util.Map; import java.util.TreeMap; +/** + * Declarative update packaged as an {@link Item} and applied by {@link org.apache.unomi.api.services.PatchService}. + * {@link #PATCHABLE_TYPES} lists supported definition types (segments, rules, etc.). + * The {@link #operation} is {@code override} (replace item), {@code patch} (JSON Patch payload), + * or {@code remove} (delete target; no payload required). + */ public class Patch extends Item { private static final long serialVersionUID = 4171966405850833985L; + /** + * A map containing the types of items that are capable of being patched. + * The keys represent item type names, and the values are {@link Class} + * objects corresponding to those patchable item types. + */ public final static Map> PATCHABLE_TYPES; static { @@ -47,6 +58,9 @@ public class Patch extends Item { PATCHABLE_TYPES.put("scoring", Scoring.class); } + /** + * The constant string used to identify this class as a "patch" item type. + */ public static final String ITEM_TYPE = "patch"; private String patchedItemId; @@ -61,36 +75,55 @@ public class Patch extends Item { /** * Get the id of the item that will be concerned by this patch + * * @return item id */ public String getPatchedItemId() { return patchedItemId; } + /** + * Sets the ID of the item that will be concerned by this patch. + * + * @param patchedItemId the id of the item + */ public void setPatchedItemId(String patchedItemId) { this.patchedItemId = patchedItemId; } /** * Get the item type of the item that will be concerned by this patch + * * @return item type */ public String getPatchedItemType() { return patchedItemType; } + /** + * Sets the item type of the item that will be concerned by this patch. + * + * @param patchedItemType the item type + */ public void setPatchedItemType(String patchedItemType) { this.patchedItemType = patchedItemType; } /** * Get the type of patch operation : override, patch or remove + * * @return operation */ public String getOperation() { return operation; } + /** + * Sets the type of patch operation to perform. This can typically be + * 'override', 'patch', or 'remove'. + * + * @param operation the patch operation type + */ public void setOperation(String operation) { this.operation = operation; } @@ -100,24 +133,40 @@ public void setOperation(String operation) { * For override operation, the data is the full item * For patch, the data is a JsonPatch object * For remove, no data is needed + * * @return data */ public Object getData() { return data; } + /** + * Sets the data payload for the patch. + * For override operations, this should contain the full + * item representation. + * For patch operations, this should contain a JsonPatch object. + * For remove operations, no data is required. + * + * @param data the patch data + */ public void setData(Object data) { this.data = data; } /** * Get the date of the last patch application + * * @return last application date */ public Date getLastApplication() { return lastApplication; } + /** + * Sets the date when the patch was last applied. + * + * @param lastApplication the date of the last application + */ public void setLastApplication(Date lastApplication) { this.lastApplication = lastApplication; } diff --git a/api/src/main/java/org/apache/unomi/api/Persona.java b/api/src/main/java/org/apache/unomi/api/Persona.java index 284ecb250..1b20cbc4c 100644 --- a/api/src/main/java/org/apache/unomi/api/Persona.java +++ b/api/src/main/java/org/apache/unomi/api/Persona.java @@ -23,13 +23,27 @@ */ public class Persona extends Profile { + /** + * The fixed item type identifier used when representing a persona object. + */ public static final String ITEM_TYPE = "persona"; private static final long serialVersionUID = -1239061113528609426L; + /** + * Constructs a new, default {@link Persona} instance. + * This constructor initializes the persona without an explicit ID, + * allowing the item ID to potentially be set subsequently. + */ public Persona() { super(); } + /** + * Constructs a new {@link Persona} instance with the + * specified unique identifier. + * + * @param personaId The unique ID to assign to this persona. + */ public Persona(String personaId) { super(personaId); } diff --git a/api/src/main/java/org/apache/unomi/api/PersonaSession.java b/api/src/main/java/org/apache/unomi/api/PersonaSession.java index 0c9373dc8..a1f29244f 100644 --- a/api/src/main/java/org/apache/unomi/api/PersonaSession.java +++ b/api/src/main/java/org/apache/unomi/api/PersonaSession.java @@ -20,15 +20,38 @@ import java.util.Date; /** - * A Persona session. + * A browsing session attached to a {@link Persona}. + * Personas are test or synthetic profiles; their sessions let marketers + * preview personalized content as if they were a visitor in that category. */ public class PersonaSession extends Session { + /** + * The constant string identifier used as the item type for + * {@link PersonaSession} instances. + */ public static final String ITEM_TYPE = "personaSession"; private static final long serialVersionUID = -1499107289607498852L; + /** + * Constructs an empty {@link PersonaSession} instance. + * This session must be fully configured and saved using persistence + * services to represent a valid persona session record. + */ public PersonaSession() { } + /** + * Constructs a {@link PersonaSession} associated with a given profile, + * item ID, and timestamp. + * The resulting session is initialized with a system scope metadata. + * + * @param itemId the unique identifier for the persona session item. + * @param profile the {@link Profile} object representing the + * context of the session. + * + * @param timeStamp the date and time when this session was + * created or recorded. + */ public PersonaSession(String itemId, Profile profile, Date timeStamp) { super(itemId, profile, timeStamp, Metadata.SYSTEM_SCOPE); } diff --git a/api/src/main/java/org/apache/unomi/api/PersonaWithSessions.java b/api/src/main/java/org/apache/unomi/api/PersonaWithSessions.java index 0a507e9b1..0c6142c89 100644 --- a/api/src/main/java/org/apache/unomi/api/PersonaWithSessions.java +++ b/api/src/main/java/org/apache/unomi/api/PersonaWithSessions.java @@ -22,37 +22,73 @@ import java.util.List; /** - * A convenience object gathering a {@link Persona} and its associated {@link PersonaSession}s. + * Bundles a {@link Persona} with its related {@link PersonaSession} list. + * REST and tooling use this wrapper when returning a persona together with + * the sessions needed to simulate or inspect that persona. */ public class PersonaWithSessions implements Serializable { private Persona persona; private List sessions; + /** + * Default constructor. + */ public PersonaWithSessions() { } + /** + * Creates a persona bundled with its sessions. + * + * @param persona the persona + * @param sessions related persona sessions + */ public PersonaWithSessions(Persona persona, List sessions) { this.persona = persona; this.sessions = sessions; } + /** + * The persona being simulated or inspected. + * + * @return the persona + */ public Persona getPersona() { return persona; } + /** + * Sets the persona. + * + * @param persona the persona + */ public void setPersona(Persona persona) { this.persona = persona; } + /** + * Sessions linked to the persona. + * + * @return the persona sessions + */ public List getSessions() { return sessions; } + /** + * Sets the persona sessions. + * + * @param sessions the session list + */ public void setSessions(List sessions) { this.sessions = sessions; } + /** + * Most recent session, taken as the first element of {@link #getSessions()}. + * + * @return the latest session, or {@code null} if none exist + */ @XmlTransient public PersonaSession getLastSession() { return sessions.size()>0?sessions.get(0):null; diff --git a/api/src/main/java/org/apache/unomi/api/PersonalizationResult.java b/api/src/main/java/org/apache/unomi/api/PersonalizationResult.java index 15c6ab5a5..0dab6d134 100644 --- a/api/src/main/java/org/apache/unomi/api/PersonalizationResult.java +++ b/api/src/main/java/org/apache/unomi/api/PersonalizationResult.java @@ -25,46 +25,79 @@ import java.util.Map; /** - * A class to contain the result of a personalization, containing the list of content IDs as well as a changeType to - * indicate if a profile and/or a session was modified. + * Outcome of a personalization request. + * Returns matching content ids, optional extra metadata (such as control + * group flags), and internal change codes when the resolved experience + * updated the profile or session. */ public class PersonalizationResult implements Serializable { + /** + * Key used in {@link #getAdditionalResultInfos()} to indicate if the + * personalization result was generated while running in a control group. + */ public final static String ADDITIONAL_RESULT_INFO_IN_CONTROL_GROUP = "inControlGroup"; + /** Matching content identifiers for the resolved personalization. */ List contentIds; + /** Extra key/value metadata returned to the client (for example control group flags). */ Map additionalResultInfos = new HashMap<>(); + /** Internal change flags when resolution updated the profile or session. */ int changeType = EventService.NO_CHANGE; + /** + * Constructs an empty PersonalizationResult with default values. + */ public PersonalizationResult() { } + /** + * Constructs a PersonalizationResult initialized with a + * list of content IDs. + * + * @param contentIds the list of matching ids for current personalization + */ public PersonalizationResult(List contentIds) { this.contentIds = contentIds; } /** * List of matching ids for current personalization + * * @return the list of matching ids */ public List getContentIds() { return contentIds; } + /** + * Sets the list of content IDs associated with this result. + * This overwrites any previously set content IDs. + * + * @param contentIds the new list of content IDs + */ public void setContentIds(List contentIds) { this.contentIds = contentIds; } /** * Useful open map to return additional result information to the client + * * @return map of key/value pair for additional information, like: inControlGroup */ public Map getAdditionalResultInfos() { return additionalResultInfos; } + /** + * Sets the map containing additional result information. This map is useful + * for returning extra data to the client. + * + * @param additionalResultInfos a map of key/value pair for additional + * information, like: inControlGroup + */ public void setAdditionalResultInfos(Map additionalResultInfos) { this.additionalResultInfos = additionalResultInfos; } @@ -83,6 +116,14 @@ public boolean isInControlGroup() { (Boolean) additionalResultInfos.get(ADDITIONAL_RESULT_INFO_IN_CONTROL_GROUP); } + /** + * Sets whether this personalization result belongs to a control group by + * storing the boolean value in the internal additional result info map. + * + * @param inControlGroup true if the current profile or session is in + * control group for the + * personalization, false otherwise + */ public void setInControlGroup(boolean inControlGroup) { this.additionalResultInfos.put(ADDITIONAL_RESULT_INFO_IN_CONTROL_GROUP, inControlGroup); } @@ -98,6 +139,14 @@ public int getChangeType() { return changeType; } + /** + * Adds specified change flags to the current accumulated change type. + * This method uses bitwise OR operation to ensure that multiple changes + * are recorded without overwriting previous ones. + * + * @param changes The change code or flag(s) to add to the + * result's change type. + */ public void addChanges(int changes) { this.changeType |= changes; } diff --git a/api/src/main/java/org/apache/unomi/api/PluginType.java b/api/src/main/java/org/apache/unomi/api/PluginType.java index 1b75bff09..ee28d7bea 100644 --- a/api/src/main/java/org/apache/unomi/api/PluginType.java +++ b/api/src/main/java/org/apache/unomi/api/PluginType.java @@ -18,21 +18,24 @@ package org.apache.unomi.api; /** - * The interface for unomi plugins. + * Common contract for pluggable Unomi definition types loaded from OSGi. + * Implementations expose a stable id and an OSGi target filter so the runtime + * can locate executors and validators (for example {@link PropertyMergeStrategyType} + * and {@link ValueType}). */ public interface PluginType { /** - * Retrieves the plugin identifier, corresponding to the identifier of the OSGi bundle implementing the plugin. + * OSGi bundle id of the plugin that registered this type. * - * @return the plugin identifier, corresponding to the identifier of the OSGi bundle implementing the plugin + * @return the plugin bundle id */ long getPluginId(); /** - * Associates this plugin with its associated OSGi bundle identifier. + * Associates this plugin type with its OSGi bundle. * - * @param pluginId the OSGi bundle identifier associated with this plugin + * @param pluginId the plugin bundle id */ void setPluginId(long pluginId); diff --git a/api/src/main/java/org/apache/unomi/api/Profile.java b/api/src/main/java/org/apache/unomi/api/Profile.java index 76d9d63c4..5a8ee35d3 100644 --- a/api/src/main/java/org/apache/unomi/api/Profile.java +++ b/api/src/main/java/org/apache/unomi/api/Profile.java @@ -64,13 +64,13 @@ public class Profile extends Item implements SystemPropertiesItem { private Map consents = new LinkedHashMap<>(); /** - * Instantiates a new Profile. + * Default constructor. */ public Profile() { } /** - * Instantiates a new Profile with the specified identifier. + * Creates a profile with the given identifier. * * @param profileId the profile identifier */ @@ -90,20 +90,20 @@ public void setProperty(String name, Object value) { } /** - * Retrieves the property identified by the specified name. + * Value of the named profile property. * - * @param name the name of the property to retrieve - * @return the value of the specified property or {@code null} if no such property exists + * @param name the property name + * @return the property value, or {@code null} if unset */ public Object getProperty(String name) { return properties.get(name); } /** - * Retrieves the value of the nested property identified by the specified name. + * Value of a nested property, using dot-separated path segments. * - * @param name the name of the property to be retrieved, splited in the nested properties with "." - * @return the value of the property identified by the specified name + * @param name the property path (for example {@code address.city}) + * @return the nested value, or {@code null} if any segment is missing */ public Object getNestedProperty(String name) { if (!name.contains(".")) { @@ -124,9 +124,9 @@ public Object getNestedProperty(String name) { } /** - * Retrieves a Map of all property name - value pairs for this profile. + * All user-visible profile properties. * - * @return a Map of all property name - value pairs for this profile + * @return the property map */ public Map getProperties() { return properties; @@ -142,10 +142,9 @@ public void setProperties(Map properties) { } /** - * Retrieves a Map of system property name - value pairs for this profile. System properties can be used by implementations to store non-user visible properties needed for - * internal purposes. + * Internal properties used by implementations and not shown in UIs. * - * @return a Map of system property name - value pairs for this profile + * @return the system property map */ public Map getSystemProperties() { return systemProperties; @@ -161,11 +160,11 @@ public void setSystemProperties(Map systemProperties) { } /** - * Sets a system property, overwriting an existing one if it existed. This call will also created the system - * properties hash map if it didn't exist. - * @param key the key for the system property hash map - * @param value the value for the system property hash map - * @return the previous value object if it existing. + * Sets or replaces a system property, creating the map if needed. + * + * @param key the system property name + * @param value the system property value + * @return the previous value, or {@code null} if the key was not set */ public Object setSystemProperty(String key, Object value) { if (this.systemProperties == null) { @@ -185,9 +184,9 @@ public String getScope() { } /** - * Retrieves the identifiers of the segments this profile is a member of. + * Segment ids this profile currently belongs to. * - * @return the identifiers of the segments this profile is a member of + * @return the segment ids */ public Set getSegments() { return segments; @@ -196,8 +195,6 @@ public Set getSegments() { /** * Sets the identifiers of the segments this profile is a member of. * - * TODO: should be removed from the API - * * @param segments the segments */ public void setSegments(Set segments) { @@ -206,6 +203,7 @@ public void setSegments(Set segments) { /** * @deprecated since 2.0.0 merge mechanism is now based on profile aliases, and this property is not used anymore + * @return the profile id this profile was merged with, or {@code null} */ @Deprecated public String getMergedWith() { @@ -214,6 +212,7 @@ public String getMergedWith() { /** * @deprecated since 2.0.0 merge mechanism is now based on profile aliases, and this property is not used anymore + * @param mergedWith the profile id this profile was merged with */ @Deprecated public void setMergedWith(String mergedWith) { @@ -221,16 +220,17 @@ public void setMergedWith(String mergedWith) { } /** - * Retrieves the scores associated to this profile. + * Scoring model values keyed by scoring id. * - * @return the scores associated to this profile as a Map of {@link Scoring} identifier - score pairs + * @return the score map */ public Map getScores() { return scores; } /** - * TODO: should be removed from the API + * Sets scoring model values keyed by scoring id. + * * @param scores new value for scores */ public void setScores(Map scores) { @@ -238,16 +238,18 @@ public void setScores(Map scores) { } /** - * Returns all the consents, including the revokes ones. - * @return a map that contains as a key the scope + "/" + consent type ID (or just the consent type ID if no scope was set on the consent), and the consent itself as a value + * All consents on this profile, including revoked entries. + * + * @return consent map keyed by scope/type path */ public Map getConsents() { return consents; } /** - * Returns true if this profile is an anonymous profile. - * @return true of the profile has been marked as an anonymous profile, false otherwise. + * Whether this profile is marked anonymous via the system property {@code isAnonymousProfile}. + * + * @return {@code true} if anonymous, {@code false} otherwise */ @XmlTransient public boolean isAnonymousProfile() { @@ -256,11 +258,11 @@ public boolean isAnonymousProfile() { } /** - * Set a consent into the profile. - * @param consent if the consent is REVOKED, it will try to remove a consent with the same type id if it - * exists for the profile. - * @return true if the operation was successful (inserted exception in the case of a revoked consent, in which case - * it is successful if there was a consent to revoke). + * Adds or removes a consent on this profile. + * Revoked consents remove the matching entry when present. + * + * @param consent the consent to store or revoke + * @return {@code true} if the operation changed stored consents */ @XmlTransient public boolean setConsent(Consent consent) { diff --git a/api/src/main/java/org/apache/unomi/api/ProfileAlias.java b/api/src/main/java/org/apache/unomi/api/ProfileAlias.java index 1434b029d..cdfe734b1 100644 --- a/api/src/main/java/org/apache/unomi/api/ProfileAlias.java +++ b/api/src/main/java/org/apache/unomi/api/ProfileAlias.java @@ -19,8 +19,17 @@ import java.util.Date; +/** + * Cross-application link between a client profile id and a canonical {@link Profile}. + * When the same visitor is known under different ids in separate scopes or apps, + * aliases let {@link org.apache.unomi.api.services.ProfileService} merge activity + * onto one persisted profile record. + */ public class ProfileAlias extends Item { + /** + * Item type identifier for profile aliases. + */ public static final String ITEM_TYPE = "profileAlias"; private String profileID; @@ -31,37 +40,80 @@ public class ProfileAlias extends Item { private Date modifiedTime; + /** + * Creates an empty profile alias. + */ public ProfileAlias() { } + /** + * Canonical profile id linked by this alias. + * + * @return profile id + */ public String getProfileID() { return profileID; } + /** + * Sets the canonical profile id. + * + * @param profileID profile id + */ public void setProfileID(String profileID) { this.profileID = profileID; } + /** + * Client-specific profile identifier. + * + * @return client id, or {@code null} if unset + */ public String getClientID() { return clientID; } + /** + * Sets the client-specific profile identifier. + * + * @param clientID client id + */ public void setClientID(String clientID) { this.clientID = clientID; } + /** + * When this alias was created. + * + * @return creation time + */ public Date getCreationTime() { return creationTime; } + /** + * Sets the creation time. + * + * @param creationTime creation time + */ public void setCreationTime(Date creationTime) { this.creationTime = creationTime; } + /** + * When this alias was last modified. + * + * @return last modification time, or {@code null} if unset + */ public Date getModifiedTime() { return modifiedTime; } + /** + * Sets the last modification time. + * + * @param modifiedTime last modification time + */ public void setModifiedTime(Date modifiedTime) { this.modifiedTime = modifiedTime; } diff --git a/api/src/main/java/org/apache/unomi/api/PropertyMergeStrategyExecutor.java b/api/src/main/java/org/apache/unomi/api/PropertyMergeStrategyExecutor.java index c7dcd0171..88400a945 100644 --- a/api/src/main/java/org/apache/unomi/api/PropertyMergeStrategyExecutor.java +++ b/api/src/main/java/org/apache/unomi/api/PropertyMergeStrategyExecutor.java @@ -20,7 +20,9 @@ import java.util.List; /** - * A strategy algorithm to merge profile properties such as "adding integers", "using oldest value", "using most recent value", "merging lists", etc... + * Algorithm that merges two values for the same profile property. + * Implementations define strategies such as keep latest, sum numbers, or + * union lists when events update the same field. */ public interface PropertyMergeStrategyExecutor { /** diff --git a/api/src/main/java/org/apache/unomi/api/PropertyMergeStrategyType.java b/api/src/main/java/org/apache/unomi/api/PropertyMergeStrategyType.java index 8a2249ec1..adf6000c0 100644 --- a/api/src/main/java/org/apache/unomi/api/PropertyMergeStrategyType.java +++ b/api/src/main/java/org/apache/unomi/api/PropertyMergeStrategyType.java @@ -21,7 +21,9 @@ import java.io.Serializable; /** - * A unomi plugin that defines a new property merge strategy. + * Plugin declaration for a profile property merge strategy. + * Merge strategies decide how conflicting property values are combined when + * several updates apply to the same profile field (sum, latest value, etc.). */ public class PropertyMergeStrategyType implements PluginType, Serializable { @@ -31,38 +33,59 @@ public class PropertyMergeStrategyType implements PluginType, Serializable { private long pluginId; /** - * Retrieves the identifier for this PropertyMergeStrategyType. + * Merge strategy identifier. * - * @return the identifier for this PropertyMergeStrategyType + * @return the strategy id */ public String getId() { return id; } + /** + * Sets the merge strategy identifier. + * + * @param id the strategy id + */ public void setId(String id) { this.id = id; } /** - * Retrieves the OSGi filter used to identify the implementation associated with this PropertyMergeStrategyType. Filters take the following form: - * {@code (propertyMergeStrategyExecutorId=<id>)} where {@code id} corresponds to the value of the {@code propertyMergeStrategyExecutorId} service property in the - * Blueprint service definition for this PropertyMergeStrategyType. + * OSGi LDAP filter that locates the executor for this strategy. + * Format: {@code (propertyMergeStrategyExecutorId=)} where {@code id} matches the + * {@code propertyMergeStrategyExecutorId} service property in the Blueprint definition. * - * @return the filter string used to identify the implementation associated with this PropertyMergeStrategyType + * @return the OSGi filter string */ public String getFilter() { return filter; } + /** + * Sets the OSGi filter for locating the strategy executor. + * + * @param filter the OSGi filter string + */ public void setFilter(String filter) { this.filter = filter; } + /** + * Returns the OSGi bundle id of the plugin that registered this strategy. + * Used internally when resolving merge strategy implementations. + * + * @return the plugin bundle id + */ @XmlTransient public long getPluginId() { return pluginId; } + /** + * Sets the OSGi bundle id of the plugin that registered this strategy. + * + * @param pluginId the plugin bundle id + */ public void setPluginId(long pluginId) { this.pluginId = pluginId; } diff --git a/api/src/main/java/org/apache/unomi/api/PropertyType.java b/api/src/main/java/org/apache/unomi/api/PropertyType.java index 090d7eec0..25b3117d8 100644 --- a/api/src/main/java/org/apache/unomi/api/PropertyType.java +++ b/api/src/main/java/org/apache/unomi/api/PropertyType.java @@ -26,12 +26,13 @@ import java.util.*; /** - * A user-defined profile or session property, specifying how possible values are constrained, if the value is multi-valued (a vector of values as opposed to a scalar value). + * Schema definition for a custom profile or session property. + * Declares value type, defaults, allowed ranges, merge strategy, and whether + * the property accepts multiple values. */ public class PropertyType extends MetadataItem { /** * The PropertyType ITEM_TYPE. - * * @see Item for a discussion of ITEM_TYPE */ public static final String ITEM_TYPE = "propertyType"; @@ -51,47 +52,42 @@ public class PropertyType extends MetadataItem { private Set childPropertyTypes = new LinkedHashSet<>(); /** - * Instantiates a new Property type. + * Default constructor. */ public PropertyType() { } /** - * Instantiates a new Property type with the specified Metadata. + * Creates a property type with the given metadata. * - * @param metadata the metadata associated with the specified metadata + * @param metadata the property type metadata */ public PropertyType(Metadata metadata) { super(metadata); } /** - * Retrieves the target for this property type, indicating the type of elements this property type is defined for. For example, for property types attached to profiles, {@code - * target} would be {@code "profiles"}. - * - * TODO: deprecated? + * Item type this property applies to (for example {@code profiles}). * - * @return the target for this property type + * @return the target item type */ public String getTarget() { return target; } /** - * Sets the target for this property type. - * - * TODO: deprecated? + * Sets the item type this property applies to. * - * @param target the target for this property type, indicating the type of elements this property type is defined for + * @param target the target item type */ public void setTarget(String target) { this.target = target; } /** - * Retrieves the identifier of the value type constraining values for properties using this PropertyType. + * Identifier of the {@link ValueType} that constrains property values. * - * @return the value type identifier associated with values defined by this PropertyType + * @return the value type id * @see ValueType */ @XmlElement(name = "type") @@ -102,16 +98,16 @@ public String getValueTypeId() { /** * Sets the value type identifier. * - * @param valueTypeId the value type identifier + * @param valueTypeId the value type id */ public void setValueTypeId(String valueTypeId) { this.valueTypeId = valueTypeId; } /** - * Retrieves the value type associated with values defined for properties using this PropertyType. + * Resolved value type definition for properties using this schema. * - * @return the value type associated with values defined for properties using this PropertyType + * @return the value type */ @XmlTransient public ValueType getValueType() { @@ -119,91 +115,88 @@ public ValueType getValueType() { } /** - * Sets the value type. + * Sets the resolved value type definition. * - * @param valueType the value type associated with values defined for properties using this PropertyType + * @param valueType the value type */ public void setValueType(ValueType valueType) { this.valueType = valueType; } /** - * Retrieves the default value defined for property using this PropertyType. + * Default value applied when a property is created without an explicit value. * - * @return the default value defined for property using this PropertyType + * @return the default value */ public String getDefaultValue() { return defaultValue; } /** - * Sets the default value that properties using this PropertyType will use if no value is specified explicitly. + * Sets the default value for properties of this type. * - * @param defaultValue the default value that properties using this PropertyType will use if no value is specified explicitly + * @param defaultValue the default value */ public void setDefaultValue(String defaultValue) { this.defaultValue = defaultValue; } /** - * Retrieves the set of JCR properties from which properties of this type would be automatically initialized from. + * External property names used to auto-populate values for this type. * - * TODO: remove from API? - * - * @return the name of JCR properties properties of this type would be automatically initialized from + * @return the automatic mapping source names */ public Set getAutomaticMappingsFrom() { return automaticMappingsFrom; } /** - * Specifies the set of JCR properties from which properties of this type would be automatically initialized from. - * TODO: remove from API? + * Sets the external property names used for automatic mapping. * - * @param automaticMappingsFrom the set of JCR properties from which properties of this type would be automatically initialized from + * @param automaticMappingsFrom the source property names */ public void setAutomaticMappingsFrom(Set automaticMappingsFrom) { this.automaticMappingsFrom = automaticMappingsFrom; } /** - * Retrieves the rank of this PropertyType for ordering purpose. + * Relative ordering weight when multiple property types are shown together. * - * @return the rank of this PropertyType for ordering purpose + * @return the rank */ public Double getRank() { return rank; } /** - * Specifies the rank of this PropertyType for ordering purpose. + * Sets the display ordering rank. * - * @param rank the rank of this PropertyType for ordering purpose + * @param rank the rank */ public void setRank(Double rank) { this.rank = rank; } /** - * Retrieves the identifier of the {@link PropertyMergeStrategyType} to be used in case profiles with properties using this PropertyType are being merged. + * Merge strategy id used when profiles with this property are merged. * - * @return the identifier of the {@link PropertyMergeStrategyType} to be used in case profiles with properties using this PropertyType are being merged + * @return the merge strategy id */ public String getMergeStrategy() { return mergeStrategy; } /** - * Sets the identifier of the {@link PropertyMergeStrategyType} to be used in case profiles with properties using this PropertyType are being merged + * Sets the merge strategy id for profile merges. * - * @param mergeStrategy the identifier of the {@link PropertyMergeStrategyType} to be used in case profiles with properties using this PropertyType are being merged + * @param mergeStrategy the merge strategy id */ public void setMergeStrategy(String mergeStrategy) { this.mergeStrategy = mergeStrategy; } /** - * Retrieves the date ranges. + * Allowed date ranges for values of this type. * * @return the date ranges */ @@ -212,7 +205,7 @@ public List getDateRanges() { } /** - * Sets the date ranges. + * Sets the allowed date ranges. * * @param dateRanges the date ranges */ @@ -221,7 +214,7 @@ public void setDateRanges(List dateRanges) { } /** - * Retrieves the numeric ranges. + * Allowed numeric ranges for values of this type. * * @return the numeric ranges */ @@ -230,7 +223,7 @@ public List getNumericRanges() { } /** - * Sets the numeric ranges. + * Sets the allowed numeric ranges. * * @param numericRanges the numeric ranges */ @@ -239,18 +232,18 @@ public void setNumericRanges(List numericRanges) { } /** - * Retrieves the ip ranges. + * Allowed IP ranges for values of this type. * - * @return the ip ranges + * @return the IP ranges */ public List getIpRanges() { return ipRanges; } /** - * Sets the ip ranges. + * Sets the allowed IP ranges. * - * @param ipRanges the ip ranges + * @param ipRanges the IP ranges */ public void setIpRanges(List ipRanges) { this.ipRanges = ipRanges; @@ -276,8 +269,7 @@ public void setMultivalued(Boolean multivalued) { /** * Whether properties with this type are marked as protected. Protected properties can be displayed but their value cannot be changed. - * - * TODO: rename to readOnly? + * The {@code protected} flag name may change to {@code readOnly} in a future release. * * @return {@code true} if properties of this type are protected, {@code false} otherwise */ @@ -294,10 +286,20 @@ public void setProtected(boolean protekted) { this.protekted = protekted; } + /** + * Nested property types that extend this schema. + * + * @return the child property types + */ public Set getChildPropertyTypes() { return childPropertyTypes; } + /** + * Sets the nested child property types. + * + * @param childPropertyTypes the child property types + */ public void setChildPropertyTypes(Set childPropertyTypes) { this.childPropertyTypes = childPropertyTypes; } diff --git a/api/src/main/java/org/apache/unomi/api/Scope.java b/api/src/main/java/org/apache/unomi/api/Scope.java index a8fa35137..110b0b9a0 100644 --- a/api/src/main/java/org/apache/unomi/api/Scope.java +++ b/api/src/main/java/org/apache/unomi/api/Scope.java @@ -18,7 +18,9 @@ package org.apache.unomi.api; /** - * Object representing a scope. + * Named isolation boundary for context data in Unomi. + * Profiles, sessions, events, and definitions belong to a scope (often a + * site or application) so data from one digital property does not mix with another. */ public class Scope extends MetadataItem { @@ -30,7 +32,7 @@ public class Scope extends MetadataItem { public static final String ITEM_TYPE = "scope"; /** - * Instantiates a new Scope. + * Default constructor. */ public Scope() { } diff --git a/api/src/main/java/org/apache/unomi/api/ServerInfo.java b/api/src/main/java/org/apache/unomi/api/ServerInfo.java index 7b80f9487..dd0a46d00 100644 --- a/api/src/main/java/org/apache/unomi/api/ServerInfo.java +++ b/api/src/main/java/org/apache/unomi/api/ServerInfo.java @@ -23,7 +23,10 @@ import java.util.Map; /** - * Basic information about a Unomi server + * Capability and build information exposed by a running Unomi node. + * Populated at startup and returned by health and cluster APIs so clients, + * operators, and other nodes can read version, build metadata, supported + * {@link EventInfo} types, optional capability flags, and banner logo lines. */ public class ServerInfo { @@ -39,77 +42,170 @@ public class ServerInfo { private List logoLines = new ArrayList<>(); + /** + * Creates an empty server info record. + */ public ServerInfo() { } + /** + * Unique identifier for this server instance. + * + * @return server identifier + */ public String getServerIdentifier() { return serverIdentifier; } + /** + * Sets the server identifier. + * + * @param serverIdentifier server identifier + */ public void setServerIdentifier(String serverIdentifier) { this.serverIdentifier = serverIdentifier; } + /** + * Running Unomi version string. + * + * @return server version + */ public String getServerVersion() { return serverVersion; } + /** + * Sets the server version. + * + * @param serverVersion server version + */ public void setServerVersion(String serverVersion) { this.serverVersion = serverVersion; } + /** + * Build number of this server distribution. + * + * @return build number + */ public String getServerBuildNumber() { return serverBuildNumber; } + /** + * Sets the build number. + * + * @param serverBuildNumber build number + */ public void setServerBuildNumber(String serverBuildNumber) { this.serverBuildNumber = serverBuildNumber; } + /** + * When this server build was produced. + * + * @return build date + */ public Date getServerBuildDate() { return serverBuildDate; } + /** + * Sets the build date. + * + * @param serverBuildDate build date + */ public void setServerBuildDate(Date serverBuildDate) { this.serverBuildDate = serverBuildDate; } + /** + * Server timestamp string (for example {@code YYYYMMDDHHmmss}). + * + * @return server timestamp + */ public String getServerTimestamp() { return serverTimestamp; } + /** + * Sets the server timestamp. + * + * @param serverTimestamp server timestamp + */ public void setServerTimestamp(String serverTimestamp) { this.serverTimestamp = serverTimestamp; } + /** + * Source control branch this build came from. + * + * @return SCM branch name + */ public String getServerScmBranch() { return serverScmBranch; } + /** + * Sets the SCM branch name. + * + * @param serverScmBranch SCM branch name + */ public void setServerScmBranch(String serverScmBranch) { this.serverScmBranch = serverScmBranch; } + /** + * Event types this server instance supports, with occurrence counts. + * + * @return supported event types + */ public List getEventTypes() { return eventTypes; } + /** + * Sets the supported event types. + * + * @param eventTypes event types + */ public void setEventTypes(List eventTypes) { this.eventTypes = eventTypes; } + /** + * Optional capability flags reported by the server. + * + * @return map of capability name to description + */ public Map getCapabilities() { return capabilities; } + /** + * Sets the capability map. + * + * @param capabilities capability name to description + */ public void setCapabilities(Map capabilities) { this.capabilities = capabilities; } + /** + * ASCII art lines shown in the server logo banner. + * + * @return logo lines + */ public List getLogoLines() { return logoLines; } + /** + * Sets the logo banner lines. + * + * @param logoLines logo lines + */ public void setLogoLines(List logoLines) { this.logoLines = logoLines; } diff --git a/api/src/main/java/org/apache/unomi/api/Session.java b/api/src/main/java/org/apache/unomi/api/Session.java index 8ca067720..ad986b931 100644 --- a/api/src/main/java/org/apache/unomi/api/Session.java +++ b/api/src/main/java/org/apache/unomi/api/Session.java @@ -58,18 +58,18 @@ public class Session extends Item implements TimestampedItem, SystemPropertiesIt private List originEventIds = new ArrayList<>(); /** - * Instantiates a new Session. + * Default constructor. */ public Session() { } /** - * Instantiates a new Session. + * Creates a session linked to a profile, timestamp, and scope. * - * @param itemId the identifier for this Session - * @param profile the associated {@link Profile} - * @param timeStamp the time stamp - * @param scope the scope + * @param itemId the session identifier + * @param profile the associated profile + * @param timeStamp the session start time + * @param scope the session scope */ public Session(String itemId, Profile profile, Date timeStamp, String scope) { super(itemId); @@ -80,18 +80,18 @@ public Session(String itemId, Profile profile, Date timeStamp, String scope) { } /** - * Retrieves the identifier of the associated Profile. + * Identifier of the profile linked to this session. * - * @return the identifier of the associated Profile + * @return the profile id */ public String getProfileId() { return profileId; } /** - * Retrieves the associated Profile. + * Profile associated with this session. * - * @return the associated profile + * @return the profile */ public Profile getProfile() { return profile; @@ -119,19 +119,19 @@ public void setProperty(String name, Object value) { } /** - * Retrieves the property identified by the specified name. + * Value of the named session property. * - * @param name the name of the property to retrieve - * @return the value of the specified property or {@code null} if no such property exists + * @param name the property name + * @return the property value, or {@code null} if unset */ public Object getProperty(String name) { return properties.get(name); } /** - * Retrieves a Map of all property name - value pairs. + * All user-visible session properties. * - * @return a Map of all property name - value pairs + * @return the property map */ public Map getProperties() { return properties; @@ -147,10 +147,9 @@ public void setProperties(Map properties) { } /** - * Retrieves a Map of system property name - value pairs. System properties can be used by implementations to store non-user visible properties needed for - * internal purposes. + * Internal properties used by implementations and not shown in UIs. * - * @return a Map of system property name - value pairs + * @return the system property map */ public Map getSystemProperties() { return systemProperties; @@ -166,16 +165,16 @@ public void setSystemProperties(Map systemProperties) { } /** - * Retrieves the session creation timestamp. + * When this session was created. * - * @return the session creation timestamp + * @return the creation timestamp */ public Date getTimeStamp() { return timeStamp; } /** - * Retrieves the last event date. + * Timestamp of the most recent event in this session. * * @return the last event date */ @@ -196,18 +195,18 @@ public void setLastEventDate(Date lastEventDate) { } /** - * Retrieves the duration. + * Elapsed time between session start and last event, in milliseconds. * - * @return the duration + * @return the session duration */ public int getDuration() { return duration; } /** - * Retrieves the size. + * Number of events recorded in this session. * - * @return the size + * @return the event count */ public int getSize() { return size; @@ -222,45 +221,55 @@ public void setSize(int size) { this.size = size; } + /** + * Scope this session belongs to. + * + * @return the scope id + */ public String getScope() { return scope; } + /** + * Sets the scope this session belongs to. + * + * @param scope the session scope identifier + */ public void setScope(String scope) { this.scope = scope; } /** - * Get the events types which causes the session creation + * Event types that triggered session creation. * - * @return List of event types + * @return the origin event types */ public List getOriginEventTypes() { return originEventTypes; } /** - * Set the events types which causes the session creation + * Sets the event types that triggered session creation. * - * @param originEventTypes List of event types + * @param originEventTypes the origin event types */ public void setOriginEventTypes(List originEventTypes) { this.originEventTypes = originEventTypes; } /** - * Get the events ids which causes the session creation + * Event ids that triggered session creation. * - * @return event ids + * @return the origin event ids */ public List getOriginEventIds() { return originEventIds; } /** - * Set the events ids which causes the session creation + * Sets the event ids that triggered session creation. * - * @param originEventIds List of event ids + * @param originEventIds the origin event ids */ public void setOriginEventIds(List originEventIds) { this.originEventIds = originEventIds; diff --git a/api/src/main/java/org/apache/unomi/api/SystemPropertiesItem.java b/api/src/main/java/org/apache/unomi/api/SystemPropertiesItem.java index e191562cc..45786d5aa 100644 --- a/api/src/main/java/org/apache/unomi/api/SystemPropertiesItem.java +++ b/api/src/main/java/org/apache/unomi/api/SystemPropertiesItem.java @@ -19,7 +19,9 @@ import java.util.Map; /** - * An Item that is holding system properties. + * {@link Item} that stores system-level properties in a map. + * System properties are internal or cross-cutting values kept separately + * from visitor profile properties. */ public interface SystemPropertiesItem { diff --git a/api/src/main/java/org/apache/unomi/api/TimestampedItem.java b/api/src/main/java/org/apache/unomi/api/TimestampedItem.java index 0a0799a15..a552e5ee5 100644 --- a/api/src/main/java/org/apache/unomi/api/TimestampedItem.java +++ b/api/src/main/java/org/apache/unomi/api/TimestampedItem.java @@ -20,13 +20,15 @@ import java.util.Date; /** - * A context-server entity that is timestamped. + * {@link Item} that records when it was created or last modified. + * Event and profile-like types implement this interface so retention, + * ordering, and auditing can use consistent timestamps. */ public interface TimestampedItem { /** - * Retrieves the associated timestamp. + * Creation or last-modification timestamp for timestamped items. * - * @return the associated timestamp + * @return the timestamp */ Date getTimeStamp(); } diff --git a/api/src/main/java/org/apache/unomi/api/Topic.java b/api/src/main/java/org/apache/unomi/api/Topic.java index 03d789d38..01843ad7b 100644 --- a/api/src/main/java/org/apache/unomi/api/Topic.java +++ b/api/src/main/java/org/apache/unomi/api/Topic.java @@ -16,26 +16,52 @@ */ package org.apache.unomi.api; +/** + * Named category tag stored as an {@link Item}. + * Topics group configuration or content entities so administrators can + * organize and search related objects across the context server. + */ public class Topic extends Item { + /** Defines the item type string used for Topic entities. */ public static final String ITEM_TYPE = "topic"; private String topicId; private String name; + /** + * Topic identifier. + * + * @return the topic id + */ public String getTopicId() { return topicId; } + /** + * Sets the topic identifier. + * + * @param topicId the topic id + */ public void setTopicId(String topicId) { this.topicId = topicId; } + /** + * Display name of the topic. + * + * @return the topic name + */ public String getName() { return name; } + /** + * Sets the display name. + * + * @param name the topic name + */ public void setName(String name) { this.name = name; } diff --git a/api/src/main/java/org/apache/unomi/api/ValueType.java b/api/src/main/java/org/apache/unomi/api/ValueType.java index d470a694b..95aff9dbc 100644 --- a/api/src/main/java/org/apache/unomi/api/ValueType.java +++ b/api/src/main/java/org/apache/unomi/api/ValueType.java @@ -23,7 +23,9 @@ import java.util.Set; /** - * A value type to be used to constrain property values. + * Definition of allowed values for a {@link PropertyType}. + * Value types describe validation rules, ranges, and serializers so profile + * and session properties stay consistent with their schema. */ public class ValueType implements PluginType, Serializable { @@ -34,42 +36,43 @@ public class ValueType implements PluginType, Serializable { private Set tags = new LinkedHashSet<>(); /** - * Instantiates a new Value type. + * Default constructor. */ public ValueType() { } /** - * Instantiates a new Value type with the specified identifier. + * Creates a value type with the given identifier. * - * @param id the identifier + * @param id the value type id */ public ValueType(String id) { this.id = id; } /** - * Retrieves this ValueType's identifier. + * Value type identifier. * - * @return this ValueType's identifier + * @return the value type id */ public String getId() { return id; } /** - * Sets this ValueType's identifier. + * Sets the value type identifier. * - * @param id this ValueType's identifier + * @param id the value type id */ public void setId(String id) { this.id = id; } /** - * Retrieves the {@link java.util.ResourceBundle} key used to localize this ValueType's name. + * Resource bundle key for localizing the display name. + * Defaults to {@code type.} when unset. * - * @return the {@link java.util.ResourceBundle} key used to localize this ValueType's name + * @return the name localization key */ public String getNameKey() { if (nameKey == null) { @@ -79,18 +82,19 @@ public String getNameKey() { } /** - * Sets the name key. + * Sets the name localization key. * - * @param nameKey the name key + * @param nameKey the resource bundle key */ public void setNameKey(String nameKey) { this.nameKey = nameKey; } /** - * Retrieves the {@link java.util.ResourceBundle} key used to localize this ValueType's description. + * Resource bundle key for localizing the description. + * Defaults to {@code type..description} when unset. * - * @return the {@link java.util.ResourceBundle} key used to localize this ValueType's name + * @return the description localization key */ public String getDescriptionKey() { if (descriptionKey == null) { @@ -100,36 +104,44 @@ public String getDescriptionKey() { } /** - * Sets the description key. + * Sets the description localization key. * - * @param descriptionKey the description key + * @param descriptionKey the resource bundle key */ public void setDescriptionKey(String descriptionKey) { this.descriptionKey = descriptionKey; } + /** + * {@inheritDoc} + */ + @Override @XmlTransient public long getPluginId() { return pluginId; } + /** + * {@inheritDoc} + */ + @Override public void setPluginId(long pluginId) { this.pluginId = pluginId; } /** - * Retrieves the tags used by this ValueType. + * Tags associated with this value type. * - * @return the tags used by this ValueType + * @return the tag names */ public Set getTags() { return tags; } /** - * Sets the tags used by this ValueType. + * Sets the tags for this value type. * - * @param tags the tags used by this ValueType + * @param tags the tag names */ public void setTags(Set tags) { this.tags = tags; diff --git a/api/src/main/java/org/apache/unomi/api/actions/Action.java b/api/src/main/java/org/apache/unomi/api/actions/Action.java index aeaba1bc6..7d10fe5d7 100644 --- a/api/src/main/java/org/apache/unomi/api/actions/Action.java +++ b/api/src/main/java/org/apache/unomi/api/actions/Action.java @@ -42,13 +42,13 @@ public class Action implements Serializable, YamlConvertible { private Map parameterValues = new HashMap<>(); /** - * Instantiates a new Action. + * Default constructor. */ public Action() { } /** - * Instantiates a new Action with the specified {@link ActionType} + * Creates an action with the given action type * * @param actionType the action's type */ @@ -57,7 +57,7 @@ public Action(ActionType actionType) { } /** - * Retrieves the action's type. + * Action type for this action. * * @return the action's type */ @@ -77,7 +77,7 @@ public void setActionType(ActionType actionType) { } /** - * Retrieves the identifier of the associated action type. + * Identifier of the associated action type. * * @return the identifier of the associated action type */ @@ -96,7 +96,7 @@ public void setActionTypeId(String actionTypeId) { } /** - * Retrieves the parameter values as a Map of parameter name - associated value pairs. + * Parameter values keyed by parameter name. * * @return a Map of parameter name - associated value pairs */ @@ -105,7 +105,7 @@ public Map getParameterValues() { } /** - * Sets the parameter values as a Map of parameter name - associated value pairs. + * Sets parameter values keyed by parameter name. * * @param parameterValues the parameter values as a Map of parameter name - associated value pairs */ @@ -129,6 +129,7 @@ public void setParameter(String name, Object value) { * Implements YamlConvertible interface with circular reference detection. * * @param visited set of already visited objects to prevent infinite recursion (may be null) + * @param maxDepth maximum recursion depth to prevent stack overflow * @return a Map representation of this action */ @Override diff --git a/api/src/main/java/org/apache/unomi/api/actions/ActionExecutor.java b/api/src/main/java/org/apache/unomi/api/actions/ActionExecutor.java index 6f33b9e02..4941df8bf 100644 --- a/api/src/main/java/org/apache/unomi/api/actions/ActionExecutor.java +++ b/api/src/main/java/org/apache/unomi/api/actions/ActionExecutor.java @@ -21,7 +21,9 @@ import org.apache.unomi.api.services.EventService; /** - * A piece of code that performs a specified {@link Action}, given a triggering {@link Event} + * Runtime implementation of an {@link org.apache.unomi.api.actions.Action}. + * When a rule fires, executors perform the configured action (send email, + * set property, etc.) for the triggering {@link Event}. */ public interface ActionExecutor { diff --git a/api/src/main/java/org/apache/unomi/api/actions/ActionPostExecutor.java b/api/src/main/java/org/apache/unomi/api/actions/ActionPostExecutor.java index e80d6cd95..923ba31cc 100644 --- a/api/src/main/java/org/apache/unomi/api/actions/ActionPostExecutor.java +++ b/api/src/main/java/org/apache/unomi/api/actions/ActionPostExecutor.java @@ -20,7 +20,9 @@ import org.apache.unomi.api.rules.Rule; /** - * An action to be executed after all {@link Rule}-derived have been processed. + * Hook executed after all rule actions for an event have run. + * Use post-executors for cleanup, logging, or follow-up work that must + * happen once per event after the main action chain completes. */ public interface ActionPostExecutor { /** 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 bf6fb477a..501dafd97 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 @@ -31,7 +31,9 @@ import static org.apache.unomi.api.utils.YamlUtils.toYamlValue; /** - * A type definition for {@link Action}s. + * Declarative definition of an action that rules can trigger. + * Action types describe parameters and point to the {@link ActionExecutor} + * implementation that performs the work at runtime. */ public class ActionType extends MetadataItem implements PluginType, YamlConvertible { /** Item type identifier for action types. */ @@ -43,50 +45,51 @@ public class ActionType extends MetadataItem implements PluginType, YamlConverti private long pluginId; /** - * Instantiates a new Action type. + * Default constructor. */ public ActionType() { } /** - * Instantiates a new Action type. - * @param metadata the metadata + * Creates an action type with the given metadata. + * + * @param metadata the action type metadata */ public ActionType(Metadata metadata) { super(metadata); } /** - * Retrieves the action executor. + * OSGi executor id that runs this action at runtime. * - * @return the action executor + * @return the action executor id */ public String getActionExecutor() { return actionExecutor; } /** - * Sets the action executor. + * Sets the action executor id. * - * @param actionExecutor the action executor + * @param actionExecutor the executor id */ public void setActionExecutor(String actionExecutor) { this.actionExecutor = actionExecutor; } /** - * Retrieves the parameters. + * Parameters accepted by this action type. * - * @return the parameters + * @return the parameter definitions */ public List getParameters() { return parameters; } /** - * Sets the parameters. + * Sets the parameter definitions. * - * @param parameters the parameters + * @param parameters the parameter list */ public void setParameters(List parameters) { this.parameters = parameters; diff --git a/api/src/main/java/org/apache/unomi/api/campaigns/Campaign.java b/api/src/main/java/org/apache/unomi/api/campaigns/Campaign.java index c932475c0..f28789738 100644 --- a/api/src/main/java/org/apache/unomi/api/campaigns/Campaign.java +++ b/api/src/main/java/org/apache/unomi/api/campaigns/Campaign.java @@ -26,7 +26,9 @@ import java.util.Date; /** - * A goal-oriented, time-limited marketing operation that needs to be evaluated for return on investment performance by tracking the ratio of visits to conversions. + * Time-bounded marketing program built around {@link org.apache.unomi.api.goals.Goal}s. + * Campaigns track entry conditions, duration, and conversion metrics so teams + * can measure ROI for a specific promotion or experiment. */ public class Campaign extends MetadataItem { /** @@ -51,22 +53,22 @@ public class Campaign extends MetadataItem { private String timezone; /** - * Instantiates a new Campaign. + * Default constructor. */ public Campaign() { } /** - * Instantiates a new Campaign with the specified metadata. + * Creates a campaign with the given metadata. * - * @param metadata the metadata + * @param metadata the campaign metadata */ public Campaign(Metadata metadata) { super(metadata); } /** - * Retrieves the start date for this Campaign. + * When the campaign becomes active. * * @return the start date */ @@ -75,7 +77,7 @@ public Date getStartDate() { } /** - * Sets the start date for this Campaign. + * Sets the campaign start date. * * @param startDate the start date */ @@ -84,7 +86,7 @@ public void setStartDate(Date startDate) { } /** - * Retrieves the end date for this Campaign. + * When the campaign stops being active. * * @return the end date */ @@ -93,7 +95,7 @@ public Date getEndDate() { } /** - * Sets the end date for this Campaign. + * Sets the campaign end date. * * @param endDate the end date */ @@ -102,90 +104,90 @@ public void setEndDate(Date endDate) { } /** - * Retrieves the entry condition that must be satisfied for users to be considered as taking part of this Campaign. + * Condition a profile must satisfy to enter the campaign. * - * @return the entry condition that must be satisfied for users to be considered as taking part of this Campaign + * @return the entry condition */ public Condition getEntryCondition() { return entryCondition; } /** - * Sets the entry condition that must be satisfied for users to be considered as taking part of this Campaign.. + * Sets the campaign entry condition. * - * @param entryCondition the entry condition that must be satisfied for users to be considered as taking part of this Campaign + * @param entryCondition the entry condition */ public void setEntryCondition(Condition entryCondition) { this.entryCondition = entryCondition; } /** - * Retrieves the cost incurred by this Campaign. + * Reported cost of running this campaign. * - * @return the cost incurred by this Campaign + * @return the campaign cost */ public Double getCost() { return cost; } /** - * Sets the cost incurred by this Campaign. + * Sets the campaign cost. * - * @param cost the cost incurred by this Campaign + * @param cost the campaign cost */ public void setCost(Double cost) { this.cost = cost; } /** - * Retrieves the currency associated to the Campaign's cost. + * Currency code for {@link #getCost()}. * - * @return the currency associated to the Campaign's cost + * @return the currency code */ public String getCurrency() { return currency; } /** - * Sets the currency associated to the Campaign's cost. + * Sets the currency code for the campaign cost. * - * @param currency the currency associated to the Campaign's cost + * @param currency the currency code */ public void setCurrency(String currency) { this.currency = currency; } /** - * Retrieves the identifier for this Campaign's primary {@link Goal}. + * Identifier of the primary goal tracked for this campaign. * - * @return the identifier for this Campaign's primary {@link Goal} + * @return the primary goal id */ public String getPrimaryGoal() { return primaryGoal; } /** - * Sets the identifier for this Campaign's primary {@link Goal}. + * Sets the primary goal id for this campaign. * - * @param primaryGoal the identifier for this Campaign's primary {@link Goal} + * @param primaryGoal the primary goal id */ public void setPrimaryGoal(String primaryGoal) { this.primaryGoal = primaryGoal; } /** - * Retrieves the timezone associated with this Campaign's start and end dates. + * Time zone used to interpret {@link #getStartDate()} and {@link #getEndDate()}. * - * @return the timezone associated with this Campaign's start and end dates + * @return the time zone id */ public String getTimezone() { return timezone; } /** - * Sets the timezone associated with this Campaign's start and end dates. + * Sets the time zone for campaign start and end dates. * - * @param timezone the timezone associated with this Campaign's start and end dates + * @param timezone the time zone id */ public void setTimezone(String timezone) { this.timezone = timezone; diff --git a/api/src/main/java/org/apache/unomi/api/campaigns/CampaignDetail.java b/api/src/main/java/org/apache/unomi/api/campaigns/CampaignDetail.java index db35100a8..164fec4ef 100644 --- a/api/src/main/java/org/apache/unomi/api/campaigns/CampaignDetail.java +++ b/api/src/main/java/org/apache/unomi/api/campaigns/CampaignDetail.java @@ -18,9 +18,10 @@ package org.apache.unomi.api.campaigns; /** - * Details about a {@link Campaign}. - * - * Created by kevan on 03/06/15. + * Live campaign performance snapshot built from profiles, sessions, and goals. + * Counts engaged profiles, session views and successes, linked goals, and an + * overall conversion rate. {@link org.apache.unomi.api.services.GoalsService} and campaign REST endpoints + * return this object for campaign dashboards. */ public class CampaignDetail { private long engagedProfiles = 0; @@ -31,52 +32,52 @@ public class CampaignDetail { private Campaign campaign; /** - * Instantiates a new Campaign detail. + * Creates campaign performance details for the given campaign. * - * @param campaign the campaign + * @param campaign the campaign being reported on */ public CampaignDetail(Campaign campaign) { this.campaign = campaign; } /** - * Retrieves the number of engaged profiles. + * Number of profiles that engaged with the campaign. * - * @return the number of engaged profiles + * @return the engaged profile count */ public long getEngagedProfiles() { return engagedProfiles; } /** - * Sets the number of engaged profiles. + * Sets the engaged profile count. * - * @param engagedProfiles the number of engaged profiles + * @param engagedProfiles the engaged profile count */ public void setEngagedProfiles(long engagedProfiles) { this.engagedProfiles = engagedProfiles; } /** - * Retrieves the number of goals. + * Number of goals linked to the campaign. * - * @return the number of goals + * @return the goal count */ public long getNumberOfGoals() { return numberOfGoals; } /** - * Sets the number of goals. + * Sets the goal count. * - * @param numberOfGoals the number of goals + * @param numberOfGoals the goal count */ public void setNumberOfGoals(long numberOfGoals) { this.numberOfGoals = numberOfGoals; } /** - * Retrieves the conversion rate. + * Overall conversion rate for the campaign. * * @return the conversion rate */ @@ -94,16 +95,16 @@ public void setConversionRate(double conversionRate) { } /** - * Retrieves the associated campaign. + * Campaign these metrics describe. * - * @return the associated campaign + * @return the campaign */ public Campaign getCampaign() { return campaign; } /** - * Sets the associated campaign. + * Sets the campaign reference. * * @param campaign the campaign */ @@ -112,36 +113,36 @@ public void setCampaign(Campaign campaign) { } /** - * Retrieves the number of campaign session views. + * Number of campaign sessions that recorded a view. * - * @return the number of campaign session views + * @return the session view count */ public long getCampaignSessionViews() { return campaignSessionViews; } /** - * Sets the number of campaign session views. + * Sets the campaign session view count. * - * @param campaignSessionViews the number of campaign session views + * @param campaignSessionViews the session view count */ public void setCampaignSessionViews(long campaignSessionViews) { this.campaignSessionViews = campaignSessionViews; } /** - * Retrieves the number of campaign session successes. + * Number of campaign sessions that reached a success state. * - * @return the number of campaign session successes + * @return the session success count */ public long getCampaignSessionSuccess() { return campaignSessionSuccess; } /** - * Sets the number of campaign session successes. + * Sets the campaign session success count. * - * @param campaignSessionSuccess the number of campaign session successes + * @param campaignSessionSuccess the session success count */ public void setCampaignSessionSuccess(long campaignSessionSuccess) { this.campaignSessionSuccess = campaignSessionSuccess; diff --git a/api/src/main/java/org/apache/unomi/api/campaigns/events/CampaignEvent.java b/api/src/main/java/org/apache/unomi/api/campaigns/events/CampaignEvent.java index 3591cec45..b3a1d055b 100644 --- a/api/src/main/java/org/apache/unomi/api/campaigns/events/CampaignEvent.java +++ b/api/src/main/java/org/apache/unomi/api/campaigns/events/CampaignEvent.java @@ -25,9 +25,7 @@ import java.util.Date; /** - * A specific campaign event to help analyzing your key performance indicators by marking specific dates during your campaign. - * - * @author : rincevent Created : 17/03/15 + * A campaign milestone event used to mark dates and costs during a campaign for KPI analysis. */ public class CampaignEvent extends MetadataItem { /** @@ -44,13 +42,13 @@ public class CampaignEvent extends MetadataItem { private String timezone; /** - * Instantiates a new Campaign event. + * Default constructor. */ public CampaignEvent() { } /** - * Instantiates a new Campaign event with the specified metadata. + * Creates a campaign event with the given metadata. * * @param metadata the metadata */ @@ -59,9 +57,9 @@ public CampaignEvent(Metadata metadata) { } /** - * Retrieves the cost associated with this campaign event. + * Cost associated with this campaign event. * - * @return the cost associated with this campaign event + * @return the event cost */ public Double getCost() { return cost; @@ -77,9 +75,9 @@ public void setCost(Double cost) { } /** - * Retrieves the currency. + * Currency code for the event cost. * - * @return the currency + * @return the currency code */ public String getCurrency() { return currency; @@ -95,7 +93,7 @@ public void setCurrency(String currency) { } /** - * Retrieves the event date. + * Date when this campaign event occurred. * * @return the event date */ @@ -113,9 +111,9 @@ public void setEventDate(Date eventDate) { } /** - * Retrieves the identifier of the associated {@link Campaign}. + * Identifier of the associated {@link Campaign}. * - * @return the identifier of the associated {@link Campaign} + * @return the campaign identifier */ public String getCampaignId() { return campaignId; @@ -131,9 +129,9 @@ public void setCampaignId(String campaignId) { } /** - * Retrieves the timezone. + * Timezone for interpreting the event date. * - * @return the timezone + * @return the timezone identifier */ public String getTimezone() { return timezone; diff --git a/api/src/main/java/org/apache/unomi/api/conditions/Condition.java b/api/src/main/java/org/apache/unomi/api/conditions/Condition.java index 248d1c43a..fd7655286 100644 --- a/api/src/main/java/org/apache/unomi/api/conditions/Condition.java +++ b/api/src/main/java/org/apache/unomi/api/conditions/Condition.java @@ -38,7 +38,9 @@ import static org.apache.unomi.api.utils.YamlUtils.toYamlValue; /** - * A set of elements that can be evaluated. + * Boolean expression evaluated against context (profiles, events, sessions). + * Conditions combine parameters and nested sub-conditions; segments, rules, + * and queries all use them to decide when logic applies. */ public class Condition implements Serializable, YamlConvertible { private static final long serialVersionUID = 7584522402785053206L; @@ -48,24 +50,24 @@ public class Condition implements Serializable, YamlConvertible { Map parameterValues = new HashMap<>(); /** - * Instantiates a new Condition. + * Default constructor. */ public Condition() { } /** - * Instantiates a new Condition with the specified {@link ConditionType}. + * Creates a condition with the given condition type. * - * @param conditionType the condition type + * @param conditionType the condition type definition */ public Condition(ConditionType conditionType) { setConditionType(conditionType); } /** - * Retrieves the associated condition type. + * Resolved condition type definition. * - * @return the condition type + * @return the condition type, or {@code null} if only the id is set */ @XmlTransient public ConditionType getConditionType() { @@ -85,9 +87,9 @@ public void setConditionType(ConditionType conditionType) { } /** - * Retrieves the identifier of the associated condition type. + * Identifier of the condition type backing this expression. * - * @return the identifier of the associated condition type + * @return the condition type id */ @XmlElement(name="type") public String getConditionTypeId() { @@ -104,19 +106,18 @@ public void setConditionTypeId(String conditionTypeId) { } /** - * Retrieves a Map of all parameter name - value pairs for this condition. - * - * @return a Map of all parameter name - value pairs for this condition. These depend on the condition type being used in the condition. + * Parameter values supplied for this condition instance. * + * @return the parameter map; contents depend on the condition type */ public Map getParameterValues() { return parameterValues; } /** - * Sets the parameter name - value pairs for this profile. + * Sets all parameter values for this condition. * - * @param parameterValues a Map containing the parameter name - value pairs for this profile + * @param parameterValues the parameter map */ public void setParameterValues(Map parameterValues) { this.parameterValues = parameterValues != null ? parameterValues : new HashMap<>(); @@ -133,10 +134,10 @@ public boolean containsParameter(String name) { } /** - * Retrieves the parameter identified by the specified name. + * Value of the named condition parameter. * - * @param name the name of the parameter to retrieve - * @return the value of the specified parameter or {@code null} if no such parameter exists + * @param name the parameter name + * @return the parameter value, or {@code null} if unset */ public Object getParameter(String name) { return parameterValues != null ? parameterValues.get(name) : null; 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 e1a00bda1..4adadf8e6 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 @@ -49,13 +49,14 @@ public class ConditionType extends MetadataItem implements PluginType, YamlConve private long pluginId; /** - * Instantiates a new Condition type. + * Default constructor. */ public ConditionType() { } /** - * Instantiates a new Condition type with the specified metadata + * Creates a condition type with the given metadata + * * @param metadata the metadata */ public ConditionType(Metadata metadata) { @@ -63,7 +64,7 @@ public ConditionType(Metadata metadata) { } /** - * Retrieves the condition evaluator. + * Condition evaluator implementation identifier. * * @return the condition evaluator */ @@ -81,7 +82,7 @@ public void setConditionEvaluator(String conditionEvaluator) { } /** - * Retrieves the query builder. + * Query builder implementation identifier. * * @return the query builder */ @@ -99,7 +100,7 @@ public void setQueryBuilder(String queryBuilder) { } /** - * Retrieves the parent condition. + * Parent condition for composite condition types. * * @return the parent condition */ @@ -117,7 +118,7 @@ public void setParentCondition(Condition parentCondition) { } /** - * Retrieves the defined {@link Parameter}s for this ConditionType. + * Parameters defined for this condition type. * * @return a List of the defined {@link Parameter}s for this ConditionType */ @@ -166,6 +167,7 @@ public void setPluginId(long pluginId) { * Implements YamlConvertible interface with circular reference detection. * * @param visited set of already visited objects to prevent infinite recursion (may be null) + * @param maxDepth maximum recursion depth to prevent stack overflow * @return a Map representation of this condition type */ @Override 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 9dfd5feaf..2b449dd39 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 @@ -26,7 +26,9 @@ import static org.apache.unomi.api.utils.YamlUtils.setToSortedList; /** - * Validation metadata for condition parameters + * Rules that constrain values allowed in a {@link Condition} parameter. + * Segment builders and the validation service use this metadata to reject + * invalid parameter values before save. */ public class ConditionValidation implements Serializable, YamlConvertible { private static final long serialVersionUID = 1L; @@ -63,7 +65,7 @@ public enum Type { private transient Class customType; /** - * Instantiates a new ConditionValidation. + * Default constructor. */ public ConditionValidation() { } @@ -217,6 +219,7 @@ public void setCustomType(Class customType) { * Implements YamlConvertible interface. * * @param visited set of already visited objects to prevent infinite recursion (may be null) + * @param maxDepth maximum recursion depth to prevent stack overflow * @return a Map representation of this validation */ @Override 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 8bd908a2c..fdca53bc7 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 @@ -18,19 +18,21 @@ package org.apache.unomi.api.exceptions; /** - * Exception thrown when a segment condition is invalid or cannot be used. + * Raised when a segment condition is malformed or cannot be evaluated. + * Indicates the condition JSON or references are invalid so callers can + * surface a clear error to administrators. */ public class BadSegmentConditionException extends RuntimeException { /** - * Instantiates a new BadSegmentConditionException. + * Default constructor. */ public BadSegmentConditionException() { super(); } /** - * Constructs with a message. + * Creates an exception with the given message. * * @param message the error message */ @@ -39,7 +41,7 @@ public BadSegmentConditionException(String message) { } /** - * Constructs with a message and cause. + * Creates an exception with the given message and cause. * * @param message the error message * @param cause the root cause diff --git a/api/src/main/java/org/apache/unomi/api/goals/Goal.java b/api/src/main/java/org/apache/unomi/api/goals/Goal.java index d2cac5db8..673191015 100644 --- a/api/src/main/java/org/apache/unomi/api/goals/Goal.java +++ b/api/src/main/java/org/apache/unomi/api/goals/Goal.java @@ -42,6 +42,7 @@ * */ public class Goal extends MetadataItem implements YamlConvertible { + /** The constant string used to identify this object as a goal item type. */ public static final String ITEM_TYPE = "goal"; private static final long serialVersionUID = 6131648013470949983L; private Condition startEvent; @@ -50,49 +51,72 @@ public class Goal extends MetadataItem implements YamlConvertible { private String campaignId; + /** + * Default constructor. + */ public Goal() { } + /** + * Creates a goal with the given metadata. + * + * @param metadata the goal metadata + */ public Goal(Metadata metadata) { super(metadata); } /** - * Retrieves the {@link Condition} determining the goal's start event if any, used for more complex goals where an action has to be accomplished first before evaluating the - * success of the final goal (funnel goal for example). + * Optional start condition for funnel-style goals. + * When set, goal tracking begins only after this condition is met. * - * @return the condition associated with the start event for this goal or {@code null} if no such event exists + * @return the start condition, or {@code null} if none is configured */ public Condition getStartEvent() { return startEvent; } + /** + * Sets the optional start condition for funnel-style goals. + * + * @param startEvent the start condition, or {@code null} to clear it + */ public void setStartEvent(Condition startEvent) { this.startEvent = startEvent; } /** - * Retrieves the {@link Condition} determining the target event which needs to occur to consider the goal accomplished. + * Condition that marks the goal as accomplished when it matches. * - * @return the condition associated with the event determining if the goal is reached or not + * @return the target condition */ public Condition getTargetEvent() { return targetEvent; } + /** + * Sets the condition that marks the goal as accomplished. + * + * @param targetEvent the target condition, or {@code null} to clear it + */ public void setTargetEvent(Condition targetEvent) { this.targetEvent = targetEvent; } /** - * Retrieves the identifier of the campaign this goal is part of, if any. + * Campaign id when this goal is scoped to a campaign. * - * @return the identifier of the campaign this goal is part of, or {@code null} if this goal is not part of any campaign + * @return the campaign id, or {@code null} for scope-level goals */ public String getCampaignId() { return campaignId; } + /** + * Sets the campaign id for campaign-scoped goals. + * + * @param campaignId the campaign id, or {@code null} for scope-level goals + */ public void setCampaignId(String campaignId) { this.campaignId = campaignId; } diff --git a/api/src/main/java/org/apache/unomi/api/goals/GoalReport.java b/api/src/main/java/org/apache/unomi/api/goals/GoalReport.java index 1d4413805..11a442325 100644 --- a/api/src/main/java/org/apache/unomi/api/goals/GoalReport.java +++ b/api/src/main/java/org/apache/unomi/api/goals/GoalReport.java @@ -21,34 +21,60 @@ import java.util.List; /** - * Report information about a {@link Goal}. + * Goal conversion report returned by {@link org.apache.unomi.api.services.GoalsService}. + * {@link #globalStats} aggregates all traffic; {@link #split} breaks the same + * metrics down per experiment or variant key so marketers can compare branches + * of a goal or campaign. */ public class GoalReport implements Serializable { private static final long serialVersionUID = -9150361970326342064L; private Stat globalStats; private List split; + /** + * Creates an empty goal report. + */ public GoalReport() { } + /** + * Aggregated statistics across all traffic. + * + * @return global statistics + */ public Stat getGlobalStats() { return globalStats; } + /** + * Sets the global statistics. + * + * @param globalStats global statistics + */ public void setGlobalStats(Stat globalStats) { this.globalStats = globalStats; } + /** + * Per-split statistics (for example A/B variants). + * + * @return split statistics + */ public List getSplit() { return split; } + /** + * Sets the per-split statistics. + * + * @param split split statistics + */ public void setSplit(List split) { this.split = split; } /** - * Statistics + * Counts and rates for one goal report bucket. */ public static class Stat implements Serializable { private static final long serialVersionUID = 4306277648074263098L; @@ -58,48 +84,101 @@ public static class Stat implements Serializable { private double conversionRate; private double percentage; + /** + * Creates an empty stat bucket. + */ public Stat() { } + /** + * Bucket key (for example a split name). + * + * @return stat key + */ public String getKey() { return key; } + /** + * Sets the bucket key. + * + * @param key stat key + */ public void setKey(String key) { this.key = key; } + /** + * Number of goal starts. + * + * @return start count + */ public long getStartCount() { return startCount; } + /** + * Sets the start count. + * + * @param startCount start count + */ public void setStartCount(long startCount) { this.startCount = startCount; } + /** + * Number of goal completions. + * + * @return target count + */ public long getTargetCount() { return targetCount; } + /** + * Sets the target count. + * + * @param targetCount target count + */ public void setTargetCount(long targetCount) { this.targetCount = targetCount; } + /** + * Conversion rate ({@code targetCount / startCount}). + * + * @return conversion rate + */ public double getConversionRate() { return conversionRate; } + /** + * Sets the conversion rate. + * + * @param conversionRate conversion rate + */ public void setConversionRate(double conversionRate) { this.conversionRate = conversionRate; } + /** + * Share of the total as a percentage. + * + * @return percentage value + */ public double getPercentage() { return percentage; } + /** + * Sets the percentage value. + * + * @param percentage percentage value + */ public void setPercentage(double percentage) { this.percentage = percentage; } } -} \ No newline at end of file +} diff --git a/api/src/main/java/org/apache/unomi/api/lists/UserList.java b/api/src/main/java/org/apache/unomi/api/lists/UserList.java index dc82b89b5..e7f11cb16 100644 --- a/api/src/main/java/org/apache/unomi/api/lists/UserList.java +++ b/api/src/main/java/org/apache/unomi/api/lists/UserList.java @@ -22,7 +22,9 @@ import org.apache.unomi.api.MetadataItem; /** - * Created by amidani on 24/03/2017. + * Named list of profile identifiers maintained for campaigns or exports. + * User lists let marketers target or exclude a fixed audience without + * defining a dynamic segment. */ public class UserList extends MetadataItem{ @@ -36,12 +38,12 @@ public class UserList extends MetadataItem{ private static final long serialVersionUID = -1384533444875433496L; /** - * Instantiates a new UserList. + * Default constructor. */ public UserList() {} /** - * Instantiates a new UserList with the specified metadata. + * Creates a user list with the given metadata. * * @param metadata the metadata */ 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 9c2f3ffca..3dc959fc2 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 @@ -42,13 +42,13 @@ public class Aggregate implements Serializable { /** - * Instantiates a new Aggregate. + * Default constructor. */ public Aggregate() { } /** - * Retrieves the aggregation type. + * 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. @@ -72,7 +72,7 @@ public void setType(String type) { } /** - * Retrieves the aggregation parameters. + * 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. @@ -96,7 +96,7 @@ public void setParameters(Map parameters) { } /** - * Retrieves the property to aggregate on. + * Property to aggregate on. * * @return the property to aggregate on */ @@ -114,7 +114,7 @@ public void setProperty(String property) { } /** - * Retrieves the numeric ranges used by {@code numericRange} aggregations. + * Numeric ranges for {@code numericRange} aggregations. * * @return the numeric ranges */ @@ -123,7 +123,7 @@ public List getNumericRanges() { } /** - * Retrieves the date ranges used by {@code dateRange} aggregations. + * Date ranges for {@code dateRange} aggregations. * * @return the date ranges */ @@ -141,7 +141,7 @@ public void setDateRanges(List dateRanges) { } /** - * Retrieves the IP ranges used by {@code ipRange} aggregations. + * IP ranges for {@code ipRange} aggregations. * * @return the IP ranges */ diff --git a/api/src/main/java/org/apache/unomi/api/query/AggregateQuery.java b/api/src/main/java/org/apache/unomi/api/query/AggregateQuery.java index 0f44b3b33..96ce0db9c 100644 --- a/api/src/main/java/org/apache/unomi/api/query/AggregateQuery.java +++ b/api/src/main/java/org/apache/unomi/api/query/AggregateQuery.java @@ -30,13 +30,13 @@ public class AggregateQuery implements Serializable { private Condition condition; /** - * Instantiates a new Aggregate query. + * Default constructor. */ public AggregateQuery() { } /** - * Instantiates a new Aggregate query with the specified {@link Aggregate}. + * Creates an aggregate query with the given aggregate specification. * * @param aggregate the aggregate */ @@ -45,7 +45,7 @@ public AggregateQuery(Aggregate aggregate) { } /** - * Instantiates a new Aggregate query with the specified {@link Condition}. + * Creates an aggregate query with the given filter condition. * * @param condition the condition */ @@ -54,7 +54,7 @@ public AggregateQuery(Condition condition) { } /** - * Instantiates a new Aggregate query with the specified {@link Aggregate} and {@link Condition} + * Creates an aggregate query with the given aggregate and filter condition * * @param aggregate the aggregate * @param condition the condition @@ -65,7 +65,7 @@ public AggregateQuery(Aggregate aggregate, Condition condition) { } /** - * Retrieves the aggregate. + * Aggregate specification for this query. * * @return the aggregate */ @@ -83,7 +83,7 @@ public void setAggregate(Aggregate aggregate) { } /** - * Retrieves the condition. + * Filter condition for this query. * * @return the condition */ diff --git a/api/src/main/java/org/apache/unomi/api/query/DateRange.java b/api/src/main/java/org/apache/unomi/api/query/DateRange.java index 47390b3d8..cfdfb01e5 100644 --- a/api/src/main/java/org/apache/unomi/api/query/DateRange.java +++ b/api/src/main/java/org/apache/unomi/api/query/DateRange.java @@ -20,36 +20,72 @@ import java.io.Serializable; /** - * A data range. + * Named inclusive date bucket for queries and aggregations. + * Combines a display {@code key} with {@code from} and {@code to} bounds so + * segment conditions and aggregate queries can group events or profiles inside + * a calendar window. */ public class DateRange implements Serializable { private String key; private Object from; private Object to; + /** + * Creates an empty date range. + */ public DateRange() { } + /** + * Label for this range in query results. + * + * @return range key + */ public String getKey() { return key; } + /** + * Sets the range label. + * + * @param key range key + */ public void setKey(String key) { this.key = key; } + /** + * Inclusive lower bound of the interval. + * + * @return start date or value + */ public Object getFrom() { return from; } + /** + * Sets the inclusive lower bound. + * + * @param from start date or value + */ public void setFrom(Object from) { this.from = from; } + /** + * Inclusive upper bound of the interval. + * + * @return end date or value + */ public Object getTo() { return to; } + /** + * Sets the inclusive upper bound. + * + * @param to end date or value + */ public void setTo(Object to) { this.to = to; } diff --git a/api/src/main/java/org/apache/unomi/api/query/IpRange.java b/api/src/main/java/org/apache/unomi/api/query/IpRange.java index 411f58598..a2deb51ed 100644 --- a/api/src/main/java/org/apache/unomi/api/query/IpRange.java +++ b/api/src/main/java/org/apache/unomi/api/query/IpRange.java @@ -20,36 +20,71 @@ import java.io.Serializable; /** - * An IP address range. + * Named inclusive IP range used in geo or network segment conditions. + * The {@code from} and {@code to} strings define lower and upper bounds checked + * when evaluating whether a visitor's IP address belongs to the range. */ public class IpRange implements Serializable { private String key; private String from; private String to; + /** + * Creates an empty IP range. + */ public IpRange() { } + /** + * Label for this range in query results. + * + * @return range key + */ public String getKey() { return key; } + /** + * Sets the range label. + * + * @param key range key + */ public void setKey(String key) { this.key = key; } + /** + * Inclusive lower IP bound. + * + * @return start IP address + */ public String getFrom() { return from; } + /** + * Sets the inclusive lower IP bound. + * + * @param from start IP address + */ public void setFrom(String from) { this.from = from; } + /** + * Inclusive upper IP bound. + * + * @return end IP address + */ public String getTo() { return to; } + /** + * Sets the inclusive upper IP bound. + * + * @param to end IP address + */ public void setTo(String to) { this.to = to; } diff --git a/api/src/main/java/org/apache/unomi/api/query/NumericRange.java b/api/src/main/java/org/apache/unomi/api/query/NumericRange.java index 149a35805..9a493b79b 100644 --- a/api/src/main/java/org/apache/unomi/api/query/NumericRange.java +++ b/api/src/main/java/org/apache/unomi/api/query/NumericRange.java @@ -20,36 +20,71 @@ import java.io.Serializable; /** - * A numeric range. + * Named inclusive numeric bucket for query filters and aggregations. + * Stores a {@code key} plus {@code from}/{@code to} bounds so conditions can + * test profile or event properties against numeric intervals. */ public class NumericRange implements Serializable { private String key; private Double from; private Double to; + /** + * Creates an empty numeric range. + */ public NumericRange() { } + /** + * Label for this range in query results. + * + * @return range key + */ public String getKey() { return key; } + /** + * Sets the range label. + * + * @param key range key + */ public void setKey(String key) { this.key = key; } + /** + * Inclusive lower bound. + * + * @return lower bound, or {@code null} if unset + */ public Double getFrom() { return from; } + /** + * Sets the inclusive lower bound. + * + * @param from lower bound + */ public void setFrom(Double from) { this.from = from; } + /** + * Inclusive upper bound. + * + * @return upper bound, or {@code null} if unset + */ public Double getTo() { return to; } + /** + * Sets the inclusive upper bound. + * + * @param to upper bound + */ public void setTo(Double to) { this.to = to; } diff --git a/api/src/main/java/org/apache/unomi/api/query/Query.java b/api/src/main/java/org/apache/unomi/api/query/Query.java index 48e0c1a6b..9e3e0fbe2 100644 --- a/api/src/main/java/org/apache/unomi/api/query/Query.java +++ b/api/src/main/java/org/apache/unomi/api/query/Query.java @@ -22,9 +22,10 @@ import java.io.Serializable; /** - * A query wrapper gathering all elements needed for a potentially complex CXS query: {@link Condition}, offset, limit, sorting specification, etc. - * - * Created by kevan on 14/05/15. + * Search and paging request sent to {@link org.apache.unomi.api.services.QueryService} + * and profile/segment REST endpoints. + * Combines an optional full-text filter, a {@link Condition}, sort field, offset/limit, + * and a {@code forceRefresh} flag that controls whether indexes are refreshed first. */ public class Query implements Serializable { private String text; @@ -37,134 +38,153 @@ public class Query implements Serializable { private String scrollIdentifier; /** - * Instantiates a new Query. + * Default constructor. */ public Query() { } /** - * Retrieves the text to be used in full-text searches, if any. + * Optional full-text filter applied to the search. * - * @return the text to be used in full-text searches or {@code null} if no full-text search is needed for this Query + * @return the search text, or {@code null} if none is set */ public String getText() { return text; } /** - * Sets to be used in full-text searches + * Sets the full-text filter. * - * @param text the text to be used in full-text searches or {@code null} if no full-text search is needed for this Query + * @param text the search text, or {@code null} to disable full-text search */ public void setText(String text) { this.text = text; } - /** - * Retrieves the offset of the first element to be retrieved + * Zero-based index of the first result to return. * - * @return zero or a positive integer specifying the position of the first item to be retrieved in the total ordered collection of matching items + * @return the result offset */ public int getOffset() { return offset; } /** - * Sets the offset of the first element to be retrieved + * Sets the zero-based result offset. * - * @param offset zero or a positive integer specifying the position of the first item to be retrieved in the total ordered collection of matching items + * @param offset the first result index */ public void setOffset(int offset) { this.offset = offset; } /** - * Retrieves the number of elements to retrieve. + * Maximum number of results to return, or {@code -1} for all matches. * - * @return a positive integer specifying how many matching items should be retrieved or {@code -1} if all of them should be retrieved + * @return the result limit */ public int getLimit() { return limit; } /** - * Sets the number of elements to retrieve. + * Sets the maximum number of results to return. * - * @param limit a positive integer specifying how many matching items should be retrieved or {@code -1} if all of them should be retrieved + * @param limit the result limit, or {@code -1} for all matches */ public void setLimit(int limit) { this.limit = limit; } /** - * Retrieves the sorting specifications for this Query in String format, if any. + * Sort specification as a comma-separated property list. + * Each property may be followed by {@code :asc} or {@code :desc}. * - * @return an optional ({@code null} if no sorting is required) String of comma ({@code ,}) separated property names on which ordering should be performed, ordering elements - * according to the property order in the String, considering each in turn and moving on to the next one in case of equality of all preceding ones. Each property name is - * optionally followed by a column ({@code :}) and an order specifier: {@code asc} or {@code desc}. + * @return the sort specification, or {@code null} if unsorted */ public String getSortby() { return sortby; } /** - * Sets the String representation of the sorting specifications for this Query if any. See {@link #getSortby()} method documentation for format. + * Sets the sort specification. + * See {@link #getSortby()} for the expected format. * - * @param sortby the String representation of the sorting specifications for this Query or {@code null} if no sorting is required + * @param sortby the sort specification, or {@code null} for no sorting */ public void setSortby(String sortby) { this.sortby = sortby; } /** - * Retrieves the {@link Condition} associated with this Query. + * Structured filter condition for the query. * - * @return the {@link Condition} associated with this Query + * @return the condition, or {@code null} if none is set */ public Condition getCondition() { return condition; } /** - * Sets the {@link Condition} associated with this Query. + * Sets the structured filter condition. * - * @param condition the {@link Condition} associated with this Query + * @param condition the condition */ public void setCondition(Condition condition) { this.condition = condition; } /** - * Determines whether or not an index refresh is needed before performing this Query + * Whether the search index should be refreshed before executing the query. * - * @return {@code true} if an index refresh is needed before performing this Query, {@code false} otherwise + * @return {@code true} to force a refresh, {@code false} otherwise */ public boolean isForceRefresh() { return forceRefresh; } /** - * Specifies whether or not an index refresh is needed before performing this Query. + * Sets whether to refresh the index before executing the query. * - * @param forceRefresh {@code true} if an index refresh is needed before performing this Query, {@code false} otherwise + * @param forceRefresh {@code true} to force a refresh */ public void setForceRefresh(boolean forceRefresh) { this.forceRefresh = forceRefresh; } + /** + * Scroll token for continuing a deep result set query. + * + * @return the scroll identifier, or {@code null} if scrolling is not active + */ public String getScrollIdentifier() { return scrollIdentifier; } + /** + * Sets the scroll token for continuing a scroll query. + * + * @param scrollIdentifier the scroll identifier, or {@code null} to clear it + */ public void setScrollIdentifier(String scrollIdentifier) { this.scrollIdentifier = scrollIdentifier; } + /** + * How long the scroll context remains valid (for example {@code 10m}). + * + * @return the scroll validity period, or {@code null} if not set + */ public String getScrollTimeValidity() { return scrollTimeValidity; } + /** + * Sets how long the scroll context remains valid. + * + * @param scrollTimeValidity the validity period (for example {@code 10m}) + */ public void setScrollTimeValidity(String scrollTimeValidity) { this.scrollTimeValidity = scrollTimeValidity; } diff --git a/api/src/main/java/org/apache/unomi/api/rules/Rule.java b/api/src/main/java/org/apache/unomi/api/rules/Rule.java index cd6f751e5..1e2586f18 100644 --- a/api/src/main/java/org/apache/unomi/api/rules/Rule.java +++ b/api/src/main/java/org/apache/unomi/api/rules/Rule.java @@ -64,13 +64,13 @@ public class Rule extends MetadataItem implements YamlConvertible { private int priority; /** - * Instantiates a new Rule. + * Default constructor. */ public Rule() { } /** - * Instantiates a new Rule with the specified {@link Metadata}. + * Creates a rule with the given metadata. * * @param metadata the metadata */ @@ -79,7 +79,7 @@ public Rule(Metadata metadata) { } /** - * Retrieves the condition that, when satisfied, triggers the rule. + * Condition that triggers the rule when satisfied. * * @return the condition that, when satisfied, triggers the rule. */ @@ -97,7 +97,7 @@ public void setCondition(Condition condition) { } /** - * Retrieves the actions to be performed when this rule triggers. + * Actions performed when this rule triggers. * * @return the actions to be performed when this rule triggers */ @@ -115,7 +115,7 @@ public void setActions(List actions) { } /** - * Retrieves the linked items. + * Identifiers of items linked to this rule. * * @return the linked items */ @@ -142,9 +142,9 @@ public boolean isRaiseEventOnlyOnceForProfile() { } /** - * Determines whether the event raised when the rule is triggered should only be raised once + * Whether the rule-triggered event should be raised only once per incoming event. * - * @return {@code true} if the rule-triggered event should only be raised once per profile + * @return {@code true} if the rule-triggered event should only be raised once per event */ public boolean isRaiseEventOnlyOnce() { return raiseEventOnlyOnce; @@ -187,7 +187,7 @@ public void setRaiseEventOnlyOnce(boolean raiseEventOnlyOnce) { } /** - * Retrieves the priority in case this Rule needs to be executed before other ones when similar conditions match. + * Priority when this rule must run before others with similar conditions before other ones when similar conditions match. * * @return the priority */ @@ -209,6 +209,7 @@ public void setPriority(int priority) { * Implements YamlConvertible interface with circular reference detection. * * @param visited set of already visited objects to prevent infinite recursion (may be null) + * @param maxDepth maximum recursion depth to prevent stack overflow * @return a Map representation of this rule */ @Override diff --git a/api/src/main/java/org/apache/unomi/api/rules/RuleStatistics.java b/api/src/main/java/org/apache/unomi/api/rules/RuleStatistics.java index 4dd18c79b..80cf18ac4 100644 --- a/api/src/main/java/org/apache/unomi/api/rules/RuleStatistics.java +++ b/api/src/main/java/org/apache/unomi/api/rules/RuleStatistics.java @@ -21,15 +21,18 @@ import java.util.Date; /** - * A separate item to track rule statistics, because we will manage the persistence and updating of - * these seperately from the rules themselves. This object contains all the relevant statistics - * concerning the execution of a rule, including accumulated execution times. + * Persisted statistics for a {@link org.apache.unomi.api.rules.Rule}, stored separately from the rule + * definition so counters can be updated without rewriting the rule itself. + *

+ * Cluster-wide fields ({@link #executionCount}, {@link #conditionsTime}, {@link #actionsTime}) hold + * aggregated totals synchronized across nodes. Matching {@code local*} fields track counts and + * timings on the current node since {@link #lastSyncDate}; they are merged into the cluster totals + * during synchronization. */ public class RuleStatistics extends Item { /** * The RuleStatistics ITEM_TYPE. - * * @see Item for a discussion of ITEM_TYPE */ public static final String ITEM_TYPE = "rulestats"; @@ -43,125 +46,142 @@ public class RuleStatistics extends Item { private long localActionsTime = 0; private Date lastSyncDate; + /** + * Default constructor. + */ public RuleStatistics() { } + /** + * Creates statistics for the rule with the given identifier. + * + * @param itemId the rule item identifier these statistics belong to + */ public RuleStatistics(String itemId) { super(itemId); } /** - * Retrieve the execution count of the rule in the cluster - * @return a long that is the total number of executions of the rule without the local node execution - * count + * Cluster-wide execution count (excluding the current node's unsynchronized local count). + * + * @return the cluster execution count */ public long getExecutionCount() { return executionCount; } /** - * Set the execution count of the rule in the cluster - * @param executionCount a long that represents the number of execution of the rule in the cluster + * Sets the cluster-wide execution count. + * + * @param executionCount the cluster execution count */ public void setExecutionCount(long executionCount) { this.executionCount = executionCount; } /** - * Retrieve the execution count of the rule on this single node since the last sync with the cluster - * @return a long that is the total number of executions on this node since the last sync with the - * cluster + * Execution count on this node since the last cluster synchronization. + * + * @return the local execution count */ public long getLocalExecutionCount() { return localExecutionCount; } /** - * Sets the number of local execution counts for this node since the last sync with the cluster - * @param localExecutionCount a long that represents the number of execution of the rule since the - * last sync with the cluster + * Sets the local execution count since the last cluster synchronization. + * + * @param localExecutionCount the local execution count */ public void setLocalExecutionCount(long localExecutionCount) { this.localExecutionCount = localExecutionCount; } /** - * Retrieve the accumulated time evaluating the conditions of the rule in the cluster - * @return a long representing the accumulated time in milliseconds that represents the time spent - * evaluating the conditions of the rule for the whole cluster + * Cluster-wide accumulated time spent evaluating rule conditions, in milliseconds. + * + * @return the cluster conditions time in milliseconds */ public long getConditionsTime() { return conditionsTime; } /** - * Sets the execution time of the condition of the rule for the whole cluster - * @param conditionsTime a long representing a time in milliseconds + * Sets the cluster-wide accumulated conditions evaluation time. + * + * @param conditionsTime the cluster conditions time in milliseconds */ public void setConditionsTime(long conditionsTime) { this.conditionsTime = conditionsTime; } /** - * Retrieve the accumulated execution time of the rule's condition since the last sync with the cluster - * @return a long that represents the accumulated time in milliseconds + * Local accumulated time spent evaluating rule conditions since the last cluster sync, in milliseconds. + * + * @return the local conditions time in milliseconds */ public long getLocalConditionsTime() { return localConditionsTime; } /** - * Sets the accumulated execution time of the rule's condition since the last sync with the cluster - * @param localConditionsTime a long that represents the accumulated time in milliseconds + * Sets the local accumulated conditions evaluation time since the last cluster sync. + * + * @param localConditionsTime the local conditions time in milliseconds */ public void setLocalConditionsTime(long localConditionsTime) { this.localConditionsTime = localConditionsTime; } /** - * Retrieve the accumulated time of the rule's actions - * @return a long representing the accumulated time in milliseconds + * Cluster-wide accumulated time spent executing rule actions, in milliseconds. + * + * @return the cluster actions time in milliseconds */ public long getActionsTime() { return actionsTime; } /** - * Sets the accumulated time for the rule's actions - * @param actionsTime a long representing the accumulated time in milliseconds + * Sets the cluster-wide accumulated actions execution time. + * + * @param actionsTime the cluster actions time in milliseconds */ public void setActionsTime(long actionsTime) { this.actionsTime = actionsTime; } /** - * Retrieve the accumulated time spent executing the rule's actions since the last sync with the cluster - * @return a long representing the accumulated time in milliseconds + * Local accumulated time spent executing rule actions since the last cluster sync, in milliseconds. + * + * @return the local actions time in milliseconds */ public long getLocalActionsTime() { return localActionsTime; } /** - * Sets the accumulated time spend executing the rule's actions since the last sync with the cluster - * @param localActionsTime a long representing the accumulated time in milliseconds + * Sets the local accumulated actions execution time since the last cluster sync. + * + * @param localActionsTime the local actions time in milliseconds */ public void setLocalActionsTime(long localActionsTime) { this.localActionsTime = localActionsTime; } /** - * Retrieve the last sync date - * @return a date that was set the last time the statistics were synchronized with the cluster + * Date when local counters were last merged into the cluster totals. + * + * @return the last synchronization date */ public Date getLastSyncDate() { return lastSyncDate; } /** - * Sets the last sync date - * @param lastSyncDate a date that represents the last time the statistics were synchronized - * with the cluster + * Sets the last cluster 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/security/EncryptionService.java b/api/src/main/java/org/apache/unomi/api/security/EncryptionService.java index d77c8c1e6..f3c1c2e8a 100644 --- a/api/src/main/java/org/apache/unomi/api/security/EncryptionService.java +++ b/api/src/main/java/org/apache/unomi/api/security/EncryptionService.java @@ -17,22 +17,23 @@ package org.apache.unomi.api.security; /** - * Service for handling encryption operations. + * Encrypts and decrypts sensitive tenant or configuration values. + * Implementations hide key management details from the rest of the API. */ public interface EncryptionService { /** - * Get the encryption key for a specific tenant. + * Encryption key for the specified tenant. * * @param tenantId the tenant ID - * @return the encryption key as a byte array + * @return the encryption key bytes */ byte[] getTenantEncryptionKey(String tenantId); /** - * Generate a new encryption key for a tenant. + * Generates a new encryption key for the specified tenant. * * @param tenantId the tenant ID - * @return the newly generated encryption key + * @return the newly generated encryption key bytes */ byte[] generateTenantEncryptionKey(String tenantId); -} \ No newline at end of file +} \ No newline at end of file diff --git a/api/src/main/java/org/apache/unomi/api/security/SecurityService.java b/api/src/main/java/org/apache/unomi/api/security/SecurityService.java index 6b5f92cc3..656f88f88 100644 --- a/api/src/main/java/org/apache/unomi/api/security/SecurityService.java +++ b/api/src/main/java/org/apache/unomi/api/security/SecurityService.java @@ -35,7 +35,7 @@ public interface SecurityService { String SYSTEM_TENANT = "system"; /** - * Retrieves the current subject from the security context. The subject is determined in the following order: + * Current subject from the security context. The subject is determined in the following order: * 1. JAAS context - If a JAAS authentication is active * 2. Privileged subject - If a temporary privileged operation is in progress * 3. Current request subject - The subject associated with the current request @@ -45,7 +45,7 @@ public interface SecurityService { Subject getCurrentSubject(); /** - * Retrieves the current principal from the active subject. + * Current principal from the active subject. * The principal represents the primary identity of the authenticated entity. * * @return the current principal or null if no subject is set or the subject has no principals @@ -128,7 +128,7 @@ public interface SecurityService { void executeWithPrivilegedSubject(Subject privilegedSubject, Runnable operation); /** - * Retrieves the current tenant ID based on the active subject context. + * Current tenant identifier from the active subject context. * The tenant ID is determined from the subject's principal. * * @return the current tenant ID, or SYSTEM_TENANT if operating in system context @@ -144,7 +144,7 @@ public interface SecurityService { boolean isOperatingOnSystemTenant(); /** - * Retrieves the encryption key for a specific tenant. + * Encryption key for the specified tenant. * This key is used for encrypting sensitive data within the tenant's context. * * @param tenantId the ID of the tenant whose encryption key should be retrieved @@ -207,20 +207,23 @@ public interface SecurityService { boolean hasSystemAccess(); /** - * Get the system subject with administrative privileges + * System subject with administrative privileges. + * * @return the system subject */ Subject getSystemSubject(); /** - * Extract roles from a subject + * Role names granted to the given subject. + * * @param subject the subject to extract roles from * @return set of role names */ Set extractRolesFromSubject(Subject subject); /** - * Get the security service configuration + * Security service configuration. + * * @return the security configuration */ SecurityServiceConfiguration getConfiguration(); diff --git a/api/src/main/java/org/apache/unomi/api/security/SecurityServiceConfiguration.java b/api/src/main/java/org/apache/unomi/api/security/SecurityServiceConfiguration.java index f3ccc0d31..b3f4d0c52 100644 --- a/api/src/main/java/org/apache/unomi/api/security/SecurityServiceConfiguration.java +++ b/api/src/main/java/org/apache/unomi/api/security/SecurityServiceConfiguration.java @@ -22,22 +22,85 @@ import java.util.Set; /** - * Configuration for the Security Service + * Settings that control authentication, authorization, and API access. + * Loaded at startup to configure how the security layer validates users, + * roles, and tenant principals. */ public class SecurityServiceConfiguration { - // Permission constants + /** + * Defines the permission key used when querying items, which is also + * utilized to validate the tenant ID scope for query operations. + */ public static final String PERMISSION_QUERY = "QUERY"; + /** + * Defines the permission key required for performing aggregations on item + * data, and is used internally to validate the tenant ID scope during + * aggregation queries. + */ public static final String PERMISSION_AGGREGATE = "AGGREGATE"; + /** + * Defines the permission key necessary for continuing or + * initiating a scroll query. + * This constant is used to validate the tenant ID when + * executing scroll operations. + */ public static final String PERMISSION_SCROLL_QUERY = "SCROLL_QUERY"; + /** + * Defines the permission key required for saving new items. It is also used + * internally to ensure that the correct tenant ID is set + * during save operations. + */ public static final String PERMISSION_SAVE = "SAVE"; + /** + * Defines the permission key necessary for updating existing items. This + * constant is utilized to validate the tenant ID scope when + * performing item updates. + */ public static final String PERMISSION_UPDATE = "UPDATE"; + /** + * Defines the permission key required for deleting items. It is used both + * in defining default roles and internally to validate the tenant ID during + * deletion operations. + */ public static final String PERMISSION_DELETE = "DELETE"; + /** + * Defines the permission key needed when removing multiple items + * using a query condition. + * This constant is used to validate the tenant ID scope for remove + * by query operations. + */ public static final String PERMISSION_REMOVE_BY_QUERY = "REMOVE_BY_QUERY"; + /** + * Defines the permission key required for purging data. It is utilized both + * in defining default roles and internally to validate the tenant ID + * during purge operations. + */ public static final String PERMISSION_PURGE = "PURGE"; + /** + * Defines the permission key required for performing system + * maintenance operations. + */ public static final String PERMISSION_SYSTEM_MAINTENANCE = "SYSTEM_MAINTENANCE"; + /** + * Defines the permission key needed when profile data + * encryption is required. + */ public static final String PERMISSION_ENCRYPT_PROFILE_DATA = "ENCRYPT_PROFILE_DATA"; + /** + * Defines the permission key needed when profile data decryption is + * required. This constant is used to validate the tenant ID scope + * for remove operations. + */ public static final String PERMISSION_DECRYPT_PROFILE_DATA = "DECRYPT_PROFILE_DATA"; + /** + * Defines the permission key required for writing or modifying schemas. + * This constant is used to validate access during schema write operations. + */ public static final String PERMISSION_SCHEMA_WRITE = "SCHEMA_WRITE"; + /** + * Defines the permission key required for deleting schemas. This constant + * is used to validate access during schema delete operations. + */ public static final String PERMISSION_SCHEMA_DELETE = "SCHEMA_DELETE"; private Map permissionRoles; @@ -45,6 +108,9 @@ public class SecurityServiceConfiguration { private Set systemRoles = new HashSet<>(); private boolean enableEncryption = false; + /** + * Initializes default system roles and permission-to-role mappings. + */ public SecurityServiceConfiguration() { // Initialize default system roles systemRoles.add(UnomiRoles.ADMINISTRATOR); @@ -68,51 +134,102 @@ public SecurityServiceConfiguration() { defaultRole = UnomiRoles.USER; } + /** + * Permission keys mapped to the roles allowed to perform them. + * + * @return the permission-to-roles map + */ public Map getPermissionRoles() { return permissionRoles; } + /** + * Replaces the full permission-to-role mapping. + * + * @param permissionRoles the permission-to-roles map + */ public void setPermissionRoles(Map permissionRoles) { this.permissionRoles = permissionRoles; } + /** + * Role assigned when no explicit mapping exists. + * + * @return the default role name + */ public String getDefaultRole() { return defaultRole; } + /** + * Sets the default role name. + * + * @param defaultRole the default role + */ public void setDefaultRole(String defaultRole) { this.defaultRole = defaultRole; } /** - * Get required roles for an permission - * @param permission the permission to check - * @return array of required roles, or array containing default role if permission not mapped + * Roles required for the given permission. + * + * @param permission the permission key + * @return the required roles, or an array containing the default role if unmapped */ public String[] getRequiredRolesForPermission(String permission) { return permissionRoles.getOrDefault(permission, new String[]{defaultRole}); } + /** + * Platform roles recognized by the security service. + * + * @return the system role names + */ public Set getSystemRoles() { return systemRoles; } + /** + * Replaces the set of recognized system roles. + * + * @param systemRoles the system role names + */ public void setSystemRoles(Set systemRoles) { this.systemRoles = systemRoles; } + /** + * Adds a system role name. + * + * @param role the role to add + */ public void addSystemRole(String role) { systemRoles.add(role); } + /** + * Removes a system role name. + * + * @param role the role to remove + */ public void removeSystemRole(String role) { systemRoles.remove(role); } + /** + * Whether profile data encryption is enabled. + * + * @return {@code true} if encryption is enabled + */ public boolean isEnableEncryption() { return enableEncryption; } + /** + * Enables or disables profile data encryption. + * + * @param enableEncryption {@code true} to enable encryption + */ public void setEnableEncryption(boolean enableEncryption) { this.enableEncryption = enableEncryption; } diff --git a/api/src/main/java/org/apache/unomi/api/segments/DependentMetadata.java b/api/src/main/java/org/apache/unomi/api/segments/DependentMetadata.java index 1830c1ee5..30926539f 100644 --- a/api/src/main/java/org/apache/unomi/api/segments/DependentMetadata.java +++ b/api/src/main/java/org/apache/unomi/api/segments/DependentMetadata.java @@ -22,29 +22,60 @@ import java.io.Serializable; import java.util.List; +/** + * Holds segment and scoring definitions that depend on each other. + * Used when exporting or editing related segment/scoring metadata as one + * unit in the segmentation UI. + */ public class DependentMetadata implements Serializable { private List segments; private List scorings; + /** + * Creates dependent metadata from segment and scoring definitions. + * + * @param segments metadata for dependent segments + * @param scorings metadata for dependent scorings + */ public DependentMetadata(List segments, List scorings) { this.segments = segments; this.scorings = scorings; } + /** + * Metadata for segments that depend on the edited item. + * + * @return the dependent segment metadata + */ public List getSegments() { return segments; } + /** + * Sets the dependent segment metadata. + * + * @param segments the segment metadata list + */ public void setSegments(List segments) { this.segments = segments; } + /** + * Metadata for scorings that depend on the edited item. + * + * @return the dependent scoring metadata + */ public List getScorings() { return scorings; } + /** + * Sets the dependent scoring metadata. + * + * @param scorings the scoring metadata list + */ public void setScorings(List scorings) { this.scorings = scorings; } diff --git a/api/src/main/java/org/apache/unomi/api/segments/Scoring.java b/api/src/main/java/org/apache/unomi/api/segments/Scoring.java index b32aa8ab0..85e36ad1e 100644 --- a/api/src/main/java/org/apache/unomi/api/segments/Scoring.java +++ b/api/src/main/java/org/apache/unomi/api/segments/Scoring.java @@ -45,13 +45,13 @@ public class Scoring extends MetadataItem implements YamlConvertible { private List elements; /** - * Instantiates a new Scoring. + * Default constructor. */ public Scoring() { } /** - * Instantiates a new Scoring with the specified metadata. + * Creates a scoring definition with the given metadata. * * @param metadata the metadata */ @@ -60,9 +60,9 @@ public Scoring(Metadata metadata) { } /** - * Retrieves the details of this Scoring. + * Scoring elements that define conditions and point values. * - * @return the elements + * @return the scoring elements */ public List getElements() { return elements; @@ -82,6 +82,7 @@ public void setElements(List elements) { * Implements YamlConvertible interface with circular reference detection. * * @param visited set of already visited objects to prevent infinite recursion (may be null) + * @param maxDepth maximum recursion depth to prevent stack overflow * @return a Map representation of this scoring */ @Override diff --git a/api/src/main/java/org/apache/unomi/api/segments/ScoringElement.java b/api/src/main/java/org/apache/unomi/api/segments/ScoringElement.java index ab79d132a..7467f5788 100644 --- a/api/src/main/java/org/apache/unomi/api/segments/ScoringElement.java +++ b/api/src/main/java/org/apache/unomi/api/segments/ScoringElement.java @@ -30,20 +30,22 @@ import static org.apache.unomi.api.utils.YamlUtils.toYamlValue; /** - * A scoring dimension along profiles can be evaluated and associated value to be assigned. + * One dimension of a {@link org.apache.unomi.api.segments.Scoring} model. + * Assigns a numeric value along an axis (for example engagement) when a + * profile matches the element's condition. */ public class ScoringElement implements Serializable, YamlConvertible { private Condition condition; private int value; /** - * Instantiates a new Scoring element. + * Default constructor. */ public ScoringElement() { } /** - * Retrieves the condition. + * Condition evaluated to decide whether this scoring element applies. * * @return the condition */ @@ -52,7 +54,7 @@ public Condition getCondition() { } /** - * Sets the condition. + * Sets the matching condition. * * @param condition the condition */ @@ -61,18 +63,18 @@ public void setCondition(Condition condition) { } /** - * Retrieves the value. + * Score contributed when the condition matches. * - * @return the value + * @return the score value */ public int getValue() { return value; } /** - * Sets the value. + * Sets the score value contributed when the condition matches. * - * @param value the value + * @param value the score value */ public void setValue(int value) { this.value = value; diff --git a/api/src/main/java/org/apache/unomi/api/segments/Segment.java b/api/src/main/java/org/apache/unomi/api/segments/Segment.java index 126279c15..b5a4758ab 100644 --- a/api/src/main/java/org/apache/unomi/api/segments/Segment.java +++ b/api/src/main/java/org/apache/unomi/api/segments/Segment.java @@ -49,13 +49,13 @@ public class Segment extends MetadataItem implements YamlConvertible { private Condition condition; /** - * Instantiates a new Segment. + * Default constructor. */ public Segment() { } /** - * Instantiates a new Segment with the specified metadata. + * Creates a segment with the given metadata. * * @param metadata the metadata */ @@ -64,9 +64,9 @@ public Segment(Metadata metadata) { } /** - * Retrieves the condition that users' {@link Profile} must satisfy in order to be considered member of this Segment. + * Condition that profiles must satisfy to belong to this segment. * - * @return the condition that users must match + * @return the membership condition */ public Condition getCondition() { return condition; @@ -86,6 +86,7 @@ public void setCondition(Condition condition) { * Implements YamlConvertible interface with circular reference detection. * * @param visited set of already visited objects to prevent infinite recursion (may be null) + * @param maxDepth maximum recursion depth to prevent stack overflow * @return a Map representation of this segment */ @Override diff --git a/api/src/main/java/org/apache/unomi/api/segments/SegmentsAndScores.java b/api/src/main/java/org/apache/unomi/api/segments/SegmentsAndScores.java index f9fafaf3a..0ba7dd634 100644 --- a/api/src/main/java/org/apache/unomi/api/segments/SegmentsAndScores.java +++ b/api/src/main/java/org/apache/unomi/api/segments/SegmentsAndScores.java @@ -24,37 +24,39 @@ import java.util.Set; /** - * A combination of {@link Segment} and scores (usually associated with a {@link Profile}). + * Segment memberships and scoring totals for one {@link org.apache.unomi.api.Profile}. + * {@link org.apache.unomi.api.services.ProfileService} returns this snapshot when + * clients need both current segment ids and per-scoring-plan scores in a single + * response (for example after context resolution or profile reads). */ public class SegmentsAndScores implements Serializable { private Set segments; private Map scores; /** - * Instantiates a new SegmentsAndScores. + * Creates a snapshot with the given segment memberships and scores. * - * @param segments the set of segment identifiers - * @param scores the scores as a Map of scoring name - associated score pairs + * @param segments segment identifiers + * @param scores map of scoring name to score value */ public SegmentsAndScores(Set segments, Map scores) { this.segments = segments; this.scores = scores; } - /** - * Retrieves the segments identifiers. + * Segment ids the profile belongs to. * - * @return the segments identifiers + * @return segment identifiers */ public Set getSegments() { return segments; } /** - * Retrieves the scores as a Map of scoring name - associated score pairs. + * Scoring totals keyed by scoring plan name. * - * @return the scores as a Map of scoring name - associated score pairs + * @return map of scoring name to score value */ public Map getScores() { return scores; diff --git a/api/src/main/java/org/apache/unomi/api/services/ClusterService.java b/api/src/main/java/org/apache/unomi/api/services/ClusterService.java index f100be286..7d81d095a 100644 --- a/api/src/main/java/org/apache/unomi/api/services/ClusterService.java +++ b/api/src/main/java/org/apache/unomi/api/services/ClusterService.java @@ -23,30 +23,31 @@ import java.util.List; /** - * A service to access information about the context server's cluster. - * + * Access point for cluster topology and node health. + * Returns {@link ClusterNode} records and coordinates cluster-wide + * operations such as viewing which nodes store data. */ public interface ClusterService { /** - * Retrieves the list of available nodes for this context server instance. + * Returns cluster nodes known to this instance. * - * @return a list of {@link ClusterNode} + * @return cluster nodes in the current topology */ List getClusterNodes(); /** - * Removes all data before the specified date from the context server. + * Deletes all persisted data older than the given cutoff date. * - * @param date the Date before which all data needs to be removed + * @param date cutoff; data before this date is removed */ @Deprecated void purge(final Date date); /** - * Removes all data associated with the provided scope. + * Deletes all persisted data belonging to the given scope. * - * @param scope the scope for which we want to remove data + * @param scope scope whose data should be removed */ void purge(final String scope); 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 2f77c95a8..b25cdebef 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 @@ -40,7 +40,7 @@ public interface ConditionValidationService { * ({@code script::}). Only literal parameter values are validated. * * @param condition the condition to validate - * @return a list of validation errors, empty when the condition is valid + * @return validation errors, or an empty list when valid */ List validate(Condition condition); @@ -57,7 +57,7 @@ class ValidationError { private final ValidationError parentError; /** - * Instantiates a validation error for a single parameter. + * Creates a validation error for a single parameter. * * @param parameterName the parameter that caused the error * @param message the error description @@ -68,7 +68,7 @@ public ValidationError(String parameterName, String message, ValidationErrorType } /** - * Instantiates a validation error with full condition context and optional parent error. + * Creates a validation error with full condition context and optional parent error. * * @param parameterName the parameter that caused the error * @param message the error description @@ -91,45 +91,45 @@ public ValidationError(String parameterName, String message, ValidationErrorType } /** - * Retrieves the name of the parameter that caused the error. + * Parameter that failed validation. * - * @return the parameter name, or {@code null} if not applicable + * @return parameter name, or {@code null} if not applicable */ public String getParameterName() { return parameterName; } /** - * Retrieves the error description message. + * Human-readable error description. * - * @return the error message + * @return error message */ public String getMessage() { return message; } /** - * Retrieves the type of validation error. + * Category of validation failure. * - * @return the error type + * @return error type */ public ValidationErrorType getType() { return type; } /** - * Retrieves the identifier of the condition associated with this error. + * Condition instance that failed validation. * - * @return the condition identifier, or {@code null} if not applicable + * @return condition identifier, or {@code null} if not applicable */ public String getConditionId() { return conditionId; } /** - * Retrieves the identifier of the condition type associated with this error. + * Condition type definition that was validated. * - * @return the condition type identifier, or {@code null} if not applicable + * @return condition type identifier, or {@code null} if not applicable */ public String getConditionTypeId() { return conditionTypeId; @@ -137,6 +137,8 @@ public String getConditionTypeId() { /** * @deprecated Use {@link #getConditionTypeId()} instead. + * + * @return condition type identifier (same as {@link #getConditionTypeId()}) */ @Deprecated public String getConditionTypeName() { @@ -144,27 +146,27 @@ public String getConditionTypeName() { } /** - * Retrieves a copy of the additional context map for this error. + * Defensive copy of supplemental error context. * - * @return the context map + * @return context map */ public Map getContext() { return new HashMap<>(context); } /** - * Retrieves the parent error that caused this error, if any. + * Underlying error that caused this one, when chained. * - * @return the parent error, or {@code null} if none + * @return parent error, or {@code null} if none */ public ValidationError getParentError() { return parentError; } /** - * Retrieves a detailed error message including condition, parameter, context, and parent error information. + * Builds a message with condition, parameter, context, and parent-error details. * - * @return the detailed error message + * @return detailed error message */ public String getDetailedMessage() { StringBuilder sb = new StringBuilder(); @@ -231,14 +233,19 @@ enum ValidationErrorType { private final String description; + /** + * Associates a human-readable label with this error category. + * + * @param description descriptive text for this error type + */ ValidationErrorType(String description) { this.description = description; } /** - * Retrieves the human-readable description of this error type. + * Human-readable label for this error category. * - * @return the error type description + * @return 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 d847905e5..03309e309 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 @@ -34,7 +34,7 @@ public interface ConfigSharingService { /** - * Retrieves the value of the named property. + * Value of the named property. * * @param name the property name * @return the property value, or {@code null} if not set @@ -67,7 +67,7 @@ public interface ConfigSharingService { Object removeProperty(String name); /** - * Retrieves the names of all currently known properties. + * Names of all currently known properties. * * @return the property names */ @@ -94,7 +94,7 @@ public enum ConfigChangeEventType { private Object newValue; /** - * Instantiates a configuration change event. + * Creates a configuration change event. * * @param eventType the type of change * @param name the property name @@ -109,7 +109,7 @@ public ConfigChangeEvent(ConfigChangeEventType eventType, String name, Object ol } /** - * Retrieves the type of configuration change. + * Type of configuration change. * * @return the event type */ @@ -118,7 +118,7 @@ public ConfigChangeEventType getEventType() { } /** - * Retrieves the name of the changed property. + * Name of the changed property. * * @return the property name */ @@ -127,7 +127,7 @@ public String getName() { } /** - * Retrieves the previous value of the property. + * Previous value of the property. * * @return the old value, or {@code null} for {@link ConfigChangeEventType#ADDED} events */ @@ -136,7 +136,7 @@ public Object getOldValue() { } /** - * Retrieves the new value of the property. + * New value of the property. * * @return the new value, or {@code null} for {@link ConfigChangeEventType#REMOVED} events */ diff --git a/api/src/main/java/org/apache/unomi/api/services/DefinitionsService.java b/api/src/main/java/org/apache/unomi/api/services/DefinitionsService.java index 03da8090b..dd88845c7 100644 --- a/api/src/main/java/org/apache/unomi/api/services/DefinitionsService.java +++ b/api/src/main/java/org/apache/unomi/api/services/DefinitionsService.java @@ -31,203 +31,203 @@ import java.util.Set; /** - * A service to retrieve definition information about core context server entities such as conditions, actions and values. + * Registry of built-in and plugin condition, action, and value type definitions. + * Used when loading rules from JSON, validating conditions, and resolving + * type metadata in the administration UI. */ public interface DefinitionsService { /** - * Retrieves all condition types. + * Returns every registered condition type. * - * @return a Collection of all collection types + * @return all condition types */ Collection getAllConditionTypes(); /** - * Retrieves the set of condition types with the specified tag. + * Returns condition types tagged with the given tag, including sub-tags. * - * @param tag the tag marking the condition types we want to retrieve - * @return the set of condition types with the specified tag (and its sub-tags, if specified) + * @param tag tag marking the condition types to include + * @return condition types with the specified tag */ Set getConditionTypesByTag(String tag); /** - * Retrieves the set of condition types with the specified system tag. + * Returns condition types with the given system tag, including sub-tags. * - * @param tag the system tag marking the condition types we want to retrieve - * @return the set of condition types with the specified tag (and its sub-tags, if specified) + * @param tag system tag marking the condition types to include + * @return condition types with the specified system tag */ Set getConditionTypesBySystemTag(String tag); /** - * Retrieves the condition type associated with the specified identifier. + * Looks up a condition type by id. * - * @param id the identifier of the condition type to retrieve - * @return the condition type associated with the specified identifier or {@code null} if no such condition type exists + * @param id condition type identifier + * @return matching condition type, or {@code null} if none exists */ ConditionType getConditionType(String id); /** - * Stores the condition type + * Registers or updates a condition type definition. * - * @param conditionType the condition type to store + * @param conditionType condition type to store */ void setConditionType(ConditionType conditionType); /** - * Remove the condition type + * Removes a condition type definition. * - * @param id the condition type to remove + * @param id identifier of the condition type to remove */ void removeConditionType(String id); /** - * Retrieves all known action types. + * Returns every registered action type. * - * @return all known action types + * @return all action types */ Collection getAllActionTypes(); /** - * Retrieves the set of action types with the specified tag. + * Returns action types tagged with the given tag. * - * @param tag the tag marking the action types we want to retrieve - * @return the set of action types with the specified tag + * @param tag tag marking the action types to include + * @return action types with the specified tag */ Set getActionTypeByTag(String tag); /** - * Retrieves the set of action types with the specified system tag. + * Returns action types with the given system tag. * - * @param tag the system tag marking the action types we want to retrieve - * @return the set of action types with the specified tag + * @param tag system tag marking the action types to include + * @return action types with the specified system tag */ Set getActionTypeBySystemTag(String tag); /** - * Retrieves the action type associated with the specified identifier. + * Looks up an action type by id. * - * @param id the identifier of the action type to retrieve - * @return the action type associated with the specified identifier or {@code null} if no such action type exists + * @param id action type identifier + * @return matching action type, or {@code null} if none exists */ ActionType getActionType(String id); /** - * Stores the action type + * Registers or updates an action type definition. * - * @param actionType the action type to store + * @param actionType action type to store */ void setActionType(ActionType actionType); /** - * Remove the action type + * Removes an action type definition. * - * @param id the action type to remove + * @param id identifier of the action type to remove */ void removeActionType(String id); /** - * Retrieves all known value types. + * Returns every registered value type. * - * @return all known value types + * @return all value types */ Collection getAllValueTypes(); /** - * Retrieves the set of value types with the specified tag. + * Returns value types tagged with the given tag. * - * @param tag the tag marking the value types we want to retrieve - * @return the set of value types with the specified tag + * @param tag tag marking the value types to include + * @return value types with the specified tag */ Set getValueTypeByTag(String tag); /** - * Retrieves the value type associated with the specified identifier. + * Looks up a value type by id. * - * @param id the identifier of the value type to retrieve - * @return the value type associated with the specified identifier or {@code null} if no such value type exists + * @param id value type identifier + * @return matching value type, or {@code null} if none exists */ ValueType getValueType(String id); /** - * Stores the value type + * Registers or updates a value type definition. * - * @param valueType the value type to store + * @param valueType value type to store */ void setValueType(ValueType valueType); /** - * Remove the value type + * Removes a value type definition. * - * @param id the value type to remove + * @param id identifier of the value type to remove */ void removeValueType(String id); /** - * Retrieves a Map of plugin identifier to a list of plugin types defined by that particular plugin. + * Groups registered plugin types by plugin id. * - * @return a Map of plugin identifier to a list of plugin types defined by that particular plugin + * @return map of plugin id to plugin types defined by that plugin */ Map> getTypesByPlugin(); /** - * Retrieves the property merge strategy type associated with the specified identifier. + * Looks up a property merge strategy type by id. * - * @param id the identifier of the property merge strategy type to retrieve - * @return the property merge strategy type associated with the specified identifier or {@code null} if no such property merge strategy type exists + * @param id property merge strategy type identifier + * @return matching type, or {@code null} if none exists */ PropertyMergeStrategyType getPropertyMergeStrategyType(String id); /** - * Stores the property merge strategy type + * Registers or updates a property merge strategy type. * - * @param propertyMergeStrategyType the property merge strategy type to store + * @param propertyMergeStrategyType property merge strategy type to store */ void setPropertyMergeStrategyType(PropertyMergeStrategyType propertyMergeStrategyType); /** - * Remove the property merge strategy type + * Removes a property merge strategy type definition. * - * @param id the property merge strategy type to remove + * @param id identifier of the property merge strategy type to remove */ void removePropertyMergeStrategyType(String id); /** - * Retrieves all known property merge strategy types. + * Returns every registered property merge strategy type. * - * @return all known property merge strategy types + * @return all property merge strategy types */ Collection getAllPropertyMergeStrategyTypes(); /** - * Retrieves all conditions of the specified type from the specified root condition. + * Collects nested conditions of the given type from a root condition tree. * - * TODO: remove? * - * @param rootCondition the condition from which we want to extract all conditions with the specified type - * @param typeId the identifier of the condition type we want conditions to extract to match - * @return a set of conditions contained in the specified root condition and matching the specified condition type or an empty set if no such condition exists + * @param rootCondition condition tree to walk + * @param typeId condition type id to match + * @return matching nested conditions, or an empty list if none */ List extractConditionsByType(Condition rootCondition, String typeId); /** - * Retrieves a condition matching the specified tag identifier from the specified root condition. + * Finds the first nested condition tagged with the given tag in a root condition tree. * - * TODO: remove from API and move to a different class? - * TODO: purpose and behavior not clear + * Deprecated helper that may move out of this service in a future release. * - * @param rootCondition the root condition where to start the extraction by class - * @param tag the tag to use to extract the condition - * @return Condition the condition that has been found matching the tag, or null if none matched + * @param rootCondition condition tree to walk + * @param tag tag used to select a condition + * @return first matching condition, or {@code null} if none * @deprecated As of 1.2.0-incubating, please use {@link #extractConditionBySystemTag(Condition, String)} instead */ @Deprecated Condition extractConditionByTag(Condition rootCondition, String tag); /** - * Retrieves a condition matching the specified system tag identifier from the specified root condition. + * Finds the first nested condition with the given system tag in a root condition tree. * - * @param rootCondition the root condition where to start the extraction by class - * @param systemTag the tag to use to extract the condition - * @return Condition the condition that has been found matching the tag, or null if none matched + * @param rootCondition condition tree to walk + * @param systemTag system tag used to select a condition + * @return first matching condition, or {@code null} if none */ Condition extractConditionBySystemTag(Condition rootCondition, String systemTag); @@ -261,26 +261,23 @@ public interface DefinitionsService { void refresh(); /** - * Gets the condition builder instance. + * Returns the shared condition builder for programmatic condition construction. * - * @return the condition builder instance + * @return condition builder instance */ ConditionBuilder getConditionBuilder(); /** - * Gets the TypeResolutionService instance. - * This service handles type resolution and invalid object tracking. + * Returns the type resolution service for condition and action type lookup. * - * @return the TypeResolutionService instance + * @return type resolution service instance */ TypeResolutionService getTypeResolutionService(); /** - * Gets the ConditionValidationService instance. - * This service validates conditions against their type definitions. - * The service automatically resolves condition types if needed before validation. + * Returns the condition validation service, which resolves types before validating parameters. * - * @return the ConditionValidationService instance + * @return condition validation service instance */ ConditionValidationService getConditionValidationService(); } diff --git a/api/src/main/java/org/apache/unomi/api/services/EventListenerService.java b/api/src/main/java/org/apache/unomi/api/services/EventListenerService.java index 00bccb07e..8a5a4d46a 100644 --- a/api/src/main/java/org/apache/unomi/api/services/EventListenerService.java +++ b/api/src/main/java/org/apache/unomi/api/services/EventListenerService.java @@ -20,7 +20,9 @@ import org.apache.unomi.api.Event; /** - * A service that gets notified (via {@link #onEvent(Event)}) whenever an event it can handle as decided by {@link #canHandle(Event)} occurs in the context server. + * Callback invoked for each {@link Event} the listener chooses to handle. + * Plugins implement this interface to react to events in real time alongside + * the rules engine. */ public interface EventListenerService { diff --git a/api/src/main/java/org/apache/unomi/api/services/EventService.java b/api/src/main/java/org/apache/unomi/api/services/EventService.java index d68a2c703..d6634bce2 100644 --- a/api/src/main/java/org/apache/unomi/api/services/EventService.java +++ b/api/src/main/java/org/apache/unomi/api/services/EventService.java @@ -29,7 +29,9 @@ import java.util.Set; /** - * A service to publish events, notably issued from user interactions with tracked entities, in the context server. + * Publishes and retrieves {@link Event}s in the context server. + * Client integrations send visitor actions here; rules and listeners + * consume the same event stream. */ public interface EventService { @@ -51,112 +53,106 @@ public interface EventService { int PROFILE_UPDATED = 4; /** - * Propagates the specified event in the context server, notifying - * {@link EventListenerService} instances if needed. If the event is persistent ({@link Event#isPersistent()}, it will be persisted appropriately. Once the event is - * propagated, any {@link ActionPostExecutor} the event defined will be executed and the user profile updated if needed. + * Publishes an event, notifying listeners and persisting it when marked persistent. + * Runs post-actions and updates the profile or session when rules require it. * - * @param event the Event to be propagated - * @return the result of the event handling as combination of EventService flags, to be checked using bitwise AND (&) operator + * @param event event to propagate + * @return bitmask of {@link #NO_CHANGE}, {@link #ERROR}, {@link #SESSION_UPDATED}, and {@link #PROFILE_UPDATED} */ int send(Event event); /** - * Check if the tenant is allowed to send the specified event. Restricted events must be explicitly allowed for a tenant. + * Checks whether the tenant may send the given event. + * Restricted event types must be explicitly allowed per tenant. * - * @param event event to test - * @param tenantId the ID of the tenant - * @param sourceIP the IP address from which the event was sent (not persisted for privacy) - * @return true if the event is allowed for the tenant + * @param event event to test + * @param tenantId tenant identifier + * @param sourceIP client IP (not persisted for privacy) + * @return {@code true} if the event is allowed */ boolean isEventAllowedForTenant(Event event, String tenantId, String sourceIP); /** - * Retrieves the list of available event properties. + * Returns known event property definitions. * - * @return a list of available event properties + * @return available event properties * @deprecated use event types instead */ List getEventProperties(); /** - * Retrieves the set of known event type identifiers. + * Returns ids of all registered event types. * - * @return the set of known event type identifiers. + * @return known event type identifiers */ Set getEventTypeIds(); /** - * Retrieves {@link Event}s matching the specified {@link Condition}. Events are ordered according to their time stamp ({@link Event#getTimeStamp()} and paged: only - * {@code size} of them are retrieved, starting with the {@code offset}-th one. + * Searches events matching a condition, ordered by timestamp and paged. * - * @param condition the Condition we want the Events to match to be retrieved - * @param offset zero or a positive integer specifying the position of the first event in the total ordered collection of matching events - * @param size a positive integer specifying how many matching events should be retrieved or {@code -1} if all of them should be retrieved - * @return a {@link PartialList} of matching events + * @param condition filter condition + * @param offset zero-based index of the first result + * @param size maximum results to return, or {@code -1} for all + * @return matching events */ PartialList searchEvents(Condition condition, int offset, int size); /** - * Retrieves {@link Event}s for the {@link Session} identified by the provided session identifier, matching any of the provided event types, - * ordered according to the specified {@code sortBy} String and paged: only {@code size} of them are retrieved, starting with the {@code offset}-th one. - * If a {@code query} is provided, a full text search is performed on the matching events to further filter them. + * Searches session events by type with optional full-text filtering, ordered and paged. * - * @param sessionId the identifier of the user session we're considering - * @param eventTypes an array of event type names; the events to retrieve should at least match one of these - * @param query a String to perform full text filtering on events matching the other conditions - * @param offset zero or a positive integer specifying the position of the first event in the total ordered collection of matching events - * @param size a positive integer specifying how many matching events should be retrieved or {@code -1} if all of them should be retrieved - * @param sortBy an optional ({@code null} if no sorting is required) String of comma ({@code ,}) separated property names on which ordering should be performed, ordering elements according to the property order in - * the String, considering each in turn and moving on to the next one in case of equality of all preceding ones. Each property name is optionally followed by - * a column ({@code :}) and an order specifier: {@code asc} or {@code desc}. - * @return a {@link PartialList} of matching events + * @param sessionId session identifier + * @param eventTypes event types to include (any match) + * @param query optional full-text filter, or {@code null} + * @param offset zero-based index of the first result + * @param size maximum results to return, or {@code -1} for all + * @param sortBy optional comma-separated property list with optional {@code :asc}/{@code :desc} suffixes + * @return matching events */ PartialList searchEvents(String sessionId, String[] eventTypes, String query, int offset, int size, String sortBy); /** - * Retrieves {@link Event}s matching the specified {@link Query}. + * Searches events using a structured query. * - * @param query a {@link Query} specifying which Events to retrieve - * @return a {@link PartialList} of {@code Event} instances matching the specified query + * @param query query specifying which events to return + * @return matching events */ PartialList search(Query query); - /** - * Retrieves the {@link Event} by its identifier. + * Loads an event by id. * - * @param id the identifier of the {@link Event} to retrieve - * @return the {@link Event} identified by the specified identifier or {@code null} if no such profile exists + * @param id event identifier + * @return matching event, or {@code null} if none exists */ Event getEvent(final String id); /** - * Checks whether the specified event has already been raised either for the associated session or profile depending on the specified {@code session} parameter. + * Checks whether an equivalent event was already raised for the session or profile. * - * @param event the event we want to check - * @param session {@code true} if we want to check if the specified event has already been raised for the associated session, {@code false} if we want to check - * whether the event has already been raised for the associated profile - * @return {@code true} if the event has already been raised, {@code false} otherwise + * @param event event to check + * @param session {@code true} to check the session history, {@code false} for the profile history + * @return {@code true} if a matching event already exists */ boolean hasEventAlreadyBeenRaised(Event event, boolean session); /** - * Checks whether the specified event has already been raised with the same itemId. + * Checks whether an event with the same item id was already raised. * - * @param event the event we want to check - * @return {@code true} if the event has already been raised, {@code false} otherwise + * @param event event to check + * @return {@code true} if a matching event already exists */ boolean hasEventAlreadyBeenRaised(Event event); /** - * Removes all events of the specified profile + * Deletes all events belonging to the given profile. * - * @param profileId identifier of the profile that we want to remove it's events + * @param profileId profile whose events should be removed */ void removeProfileEvents(String profileId); /** - * Delete an event by specifying its event identifier - * @param eventIdentifier the unique identifier for the event + * Deletes a single event by id. + * + * @param eventIdentifier event identifier */ void deleteEvent(String eventIdentifier); } diff --git a/api/src/main/java/org/apache/unomi/api/services/ExecutionContextManager.java b/api/src/main/java/org/apache/unomi/api/services/ExecutionContextManager.java index da1ab18a0..64011c5ac 100644 --- a/api/src/main/java/org/apache/unomi/api/services/ExecutionContextManager.java +++ b/api/src/main/java/org/apache/unomi/api/services/ExecutionContextManager.java @@ -21,24 +21,28 @@ import java.util.function.Supplier; /** - * Service interface for managing execution contexts in Unomi. + * Creates and binds {@link ExecutionContext} instances to the current thread. + * Ensures service calls run with the correct tenant and security credentials. */ public interface ExecutionContextManager { /** * Gets the current execution context. + * * @return the current execution context */ ExecutionContext getCurrentContext(); /** * Sets the current execution context. + * * @param context the context to set as current */ void setCurrentContext(ExecutionContext context); /** * Executes an operation as the system user. + * * @param operation the operation to execute * @param the return type of the operation * @return the result of the operation @@ -47,6 +51,7 @@ public interface ExecutionContextManager { /** * Executes an operation as the system user without return value. + * * @param operation the operation to execute */ void executeAsSystem(Runnable operation); @@ -54,6 +59,7 @@ public interface ExecutionContextManager { /** * Executes an operation as a specific tenant. * This method creates a tenant context, executes the operation, and ensures proper cleanup. + * * @param tenantId the ID of the tenant to execute as * @param operation the operation to execute * @param the return type of the operation @@ -64,6 +70,7 @@ public interface ExecutionContextManager { /** * Executes an operation as a specific tenant without return value. * This method creates a tenant context, executes the operation, and ensures proper cleanup. + * * @param tenantId the ID of the tenant to execute as * @param operation the operation to execute */ @@ -71,6 +78,7 @@ public interface ExecutionContextManager { /** * Creates a new execution context for the given tenant. + * * @param tenantId the tenant ID * @return the created execution context */ diff --git a/api/src/main/java/org/apache/unomi/api/services/GoalsService.java b/api/src/main/java/org/apache/unomi/api/services/GoalsService.java index 08e384e36..a4d938153 100644 --- a/api/src/main/java/org/apache/unomi/api/services/GoalsService.java +++ b/api/src/main/java/org/apache/unomi/api/services/GoalsService.java @@ -31,141 +31,143 @@ import java.util.Set; /** - * A service to interact with {@link Goal}s and {@link Campaign}s. + * CRUD and reporting API for {@link org.apache.unomi.api.goals.Goal}s and + * {@link Campaign}s. Manages goal definitions, campaign lifecycle, and + * related statistics. */ public interface GoalsService { /** - * Retrieves the set of Metadata associated with existing goals. + * Returns metadata for all goals. * - * @return the set of Metadata associated with existing goals + * @return goal metadata entries */ Set getGoalMetadatas(); /** - * Retrieves the set of Metadata associated with existing goals matching the specified {@link Query} + * Returns metadata for goals matching the given query. * - * @param query the Query used to filter the Goals which metadata we want to retrieve - * @return the set of Metadata associated with existing goals matching the specified {@link Query} + * @param query filter for goals whose metadata should be returned + * @return matching goal metadata entries */ Set getGoalMetadatas(Query query); /** - * Retrieves the goal associated with the specified identifier. + * Loads a goal by id. * - * @param goalId the identifier of the goal to retrieve - * @return the goal associated with the specified identifier or {@code null} if no such goal exists + * @param goalId goal identifier + * @return matching goal, or {@code null} if none exists */ Goal getGoal(String goalId); /** - * Saves the specified goal in the context server and creates associated {@link Rule}s if the goal is enabled. + * Saves a goal and creates associated rules when the goal is enabled. * - * TODO: rename to saveGoal + * The {@code setGoal} name is historical; a {@code saveGoal} alias may be added later. * - * @param goal the Goal to be saved + * @param goal goal to save */ void setGoal(Goal goal); /** - * Removes the goal associated with the specified identifier, also removing associated rules if needed. + * Deletes a goal and its associated rules when present. * - * @param goalId the identifier of the goal to be removed + * @param goalId goal identifier */ void removeGoal(String goalId); /** - * Retrieves the report for the goal identified with the specified identifier. + * Builds a performance report for the given goal. * - * @param goalId the identifier of the goal which report we want to retrieve - * @return the report for the specified goal + * @param goalId goal identifier + * @return goal report */ GoalReport getGoalReport(String goalId); /** - * Retrieves the report for the goal identified with the specified identifier, considering only elements determined by the specified {@link AggregateQuery}. + * Builds a performance report for the given goal, filtered by an aggregate query. * - * @param goalId the identifier of the goal which report we want to retrieve - * @param query an AggregateQuery to further specify which elements of the report we want - * @return the report for the specified goal and query + * @param goalId goal identifier + * @param query aggregate query limiting report elements + * @return goal report for the query scope */ GoalReport getGoalReport(String goalId, AggregateQuery query); /** - * Retrieves the set of Metadata associated with existing campaigns. + * Returns metadata for all campaigns. * - * @return the set of Metadata associated with existing campaigns + * @return campaign metadata entries */ Set getCampaignMetadatas(); /** - * Retrieves the set of Metadata associated with existing campaign matching the specified {@link Query} + * Returns metadata for campaigns matching the given query. * - * @param query the Query used to filter the campagins which metadata we want to retrieve - * @return the set of Metadata associated with existing campaigns matching the specified {@link Query} + * @param query filter for campaigns whose metadata should be returned + * @return matching campaign metadata entries */ Set getCampaignMetadatas(Query query); /** - * Retrieves campaign details for campaigns matching the specified query. + * Returns detailed campaign records matching the given query. * - * @param query the query specifying which campaigns to retrieve - * @return a {@link PartialList} of campaign details for the campaigns matching the specified query + * @param query filter for campaigns to return + * @return matching campaign details */ PartialList getCampaignDetails(Query query); /** - * Retrieves the {@link CampaignDetail} associated with the campaign identified with the specified identifier + * Loads detailed campaign information by id. * - * @param id the identifier of the campaign for which we want to retrieve the details - * @return the CampaignDetail for the campaign identified by the specified identifier or {@code null} if no such campaign exists + * @param id campaign identifier + * @return campaign details, or {@code null} if none exists */ CampaignDetail getCampaignDetail(String id); /** - * Retrieves the campaign identified by the specified identifier + * Loads a campaign by id. * - * @param campaignId the identifier of the campaign we want to retrieve - * @return the campaign associated with the specified identifier or {@code null} if no such campaign exists + * @param campaignId campaign identifier + * @return matching campaign, or {@code null} if none exists */ Campaign getCampaign(String campaignId); /** - * Saves the specified campaign in the context server and creates associated {@link Rule}s if the campaign is enabled. + * Saves a campaign and creates associated rules when the campaign is enabled. * - * TODO: rename to saveCampaign + * The {@code setCampaign} name is historical; a {@code saveCampaign} alias may be added later. * - * @param campaign the Campaign to be saved + * @param campaign campaign to save */ void setCampaign(Campaign campaign); /** - * Removes the campaign associated with the specified identifier, also removing associated rules if needed. + * Deletes a campaign and its associated rules when present. * - * @param campaignId the identifier of the campaign to be removed + * @param campaignId campaign identifier */ void removeCampaign(String campaignId); /** - * Retrieves {@link CampaignEvent}s matching the specified query. + * Searches campaign events matching the given query. * - * @param query the Query specifying which CampaignEvents to retrieve - * @return a {@link PartialList} of campaign events matching the specified query + * @param query filter for campaign events to return + * @return matching campaign events */ PartialList getEvents(Query query); /** - * Saves the specified campaign event in the context server. + * Saves a campaign event. * - * TODO: rename to saveCampaignEvent + * The {@code setEvent} name is historical; a {@code saveCampaignEvent} alias may be added later. * - * @param event the CampaignEvent to be saved + * @param event campaign event to save */ void setCampaignEvent(CampaignEvent event); /** - * Removes the campaign event associated with the specified identifier. + * Deletes a campaign event by id. * - * @param campaignEventId the identifier of the campaign event to be removed + * @param campaignEventId campaign event identifier */ void removeCampaignEvent(String campaignEventId); } diff --git a/api/src/main/java/org/apache/unomi/api/services/InvalidObjectInfo.java b/api/src/main/java/org/apache/unomi/api/services/InvalidObjectInfo.java index 7307fb4bd..131cb31ea 100644 --- a/api/src/main/java/org/apache/unomi/api/services/InvalidObjectInfo.java +++ b/api/src/main/java/org/apache/unomi/api/services/InvalidObjectInfo.java @@ -23,7 +23,10 @@ import java.util.concurrent.atomic.AtomicLong; /** - * Information about an invalid object, including detailed reasons for invalidation. + * Accumulated record for a definition that references missing condition or action types. + * {@link org.apache.unomi.api.services.TypeResolutionService} creates and updates + * these entries when JSON rules, segments, or other items fail type resolution. + * Operators use the missing-type lists and encounter counts to fix broken imports. */ public class InvalidObjectInfo { private final String objectType; @@ -36,12 +39,29 @@ public class InvalidObjectInfo { private final Set missingActionTypeIds; private final Set contextNames; + /** + * Creates a record with type, id, and reason only. + * + * @param objectType invalid object type + * @param objectId invalid object id + * @param reason why the object is invalid + */ public InvalidObjectInfo(String objectType, String objectId, String reason) { this(objectType, objectId, reason, null, null, null); } - public InvalidObjectInfo(String objectType, String objectId, String reason, - List missingConditionTypeIds, + /** + * Creates a record with initial missing-type and context details. + * + * @param objectType invalid object type + * @param objectId invalid object id + * @param reason why the object is invalid + * @param missingConditionTypeIds missing condition types from the first encounter, or {@code null} + * @param missingActionTypeIds missing action types from the first encounter, or {@code null} + * @param contextName context where the object was first seen, or {@code null} + */ + public InvalidObjectInfo(String objectType, String objectId, String reason, + List missingConditionTypeIds, List missingActionTypeIds, String contextName) { this.objectType = objectType; @@ -60,38 +80,83 @@ public InvalidObjectInfo(String objectType, String objectId, String reason, } } + /** + * Invalid object type. + * + * @return object type + */ public String getObjectType() { return objectType; } + /** + * Invalid object id. + * + * @return object id + */ public String getObjectId() { return objectId; } + /** + * Why the object failed validation. + * + * @return reason, or {@code null} if unset + */ public String getReason() { return reason; } + /** + * When this record was first created (milliseconds since epoch). + * + * @return first-seen timestamp + */ public long getFirstSeenTimestamp() { return firstSeenTimestamp; } + /** + * When this object was last seen as invalid (milliseconds since epoch). + * + * @return last-seen timestamp + */ public long getLastSeenTimestamp() { return lastSeenTimestamp.get(); } + /** + * How many times this invalid object has been encountered. + * + * @return encounter count + */ public int getEncounterCount() { return encounterCount.get(); } + /** + * Condition types that could not be resolved for this object. + * + * @return unmodifiable list of missing condition type ids + */ public List getMissingConditionTypeIds() { return Collections.unmodifiableList(new ArrayList<>(missingConditionTypeIds)); } + /** + * Action types that could not be resolved for this object. + * + * @return unmodifiable list of missing action type ids + */ public List getMissingActionTypeIds() { return Collections.unmodifiableList(new ArrayList<>(missingActionTypeIds)); } + /** + * Contexts where this invalid object was seen. + * + * @return unmodifiable set of context names + */ public Set getContextNames() { return Collections.unmodifiableSet(contextNames); } @@ -102,9 +167,9 @@ public Set getContextNames() { * {@code getMissingConditionTypeIds()}, {@code getMissingActionTypeIds()}, and * {@code getContextNames()} are safe during concurrent writes. * - * @param missingConditionTypeIds additional missing condition type IDs found in this encounter - * @param missingActionTypeIds additional missing action type IDs found in this encounter - * @param contextName context where this encounter occurred + * @param missingConditionTypeIds additional missing condition type ids from this encounter + * @param missingActionTypeIds additional missing action type ids from this encounter + * @param contextName context where this encounter occurred */ public void updateEncounter(List missingConditionTypeIds, List missingActionTypeIds, @@ -160,4 +225,3 @@ public String toString() { return sb.toString(); } } - diff --git a/api/src/main/java/org/apache/unomi/api/services/PersonalizationService.java b/api/src/main/java/org/apache/unomi/api/services/PersonalizationService.java index f4dea3d9d..bcd9a238c 100644 --- a/api/src/main/java/org/apache/unomi/api/services/PersonalizationService.java +++ b/api/src/main/java/org/apache/unomi/api/services/PersonalizationService.java @@ -26,37 +26,39 @@ import java.util.Map; /** - * A service to fulfill personalization request. + * Resolves which content variants to show a visitor. + * Evaluates personalization requests against profiles, sessions, and + * segments, returning {@link PersonalizationResult} with matching content ids. */ public interface PersonalizationService { /** - * Check if an item is visible for the specified profile and session + * Evaluates whether personalized content is visible for the given profile and session. * - * @param profile The profile - * @param session The session - * @param personalizedContent Personalized content, containing a list of filters - * @return If the content is visible or not + * @param profile visitor profile + * @param session visitor session + * @param personalizedContent content definition with filters to evaluate + * @return {@code true} if the content should be shown */ boolean filter(Profile profile, Session session, PersonalizedContent personalizedContent); /** - * Get the best match among a list of items, for the specified profile and session + * Selects the best-matching content variant for the given profile and session. * - * @param profile The profile - * @param session The session - * @param personalizationRequest Personalization request, containing the list of variants and the required strategy - * @return The id of the best-matching variant + * @param profile visitor profile + * @param session visitor session + * @param personalizationRequest request with variants and selection strategy + * @return id of the best-matching variant */ String bestMatch(Profile profile, Session session, PersonalizationRequest personalizationRequest); /** - * Get a personalized list, filtered and sorted, based on the profile and session + * Filters and ranks content variants for the given profile and session. * - * @param profile The profile - * @param session The session - * @param personalizationRequest Personalization request, containing the list of variants and the required strategy - * @return List of ids, based on user profile + * @param profile visitor profile + * @param session visitor session + * @param personalizationRequest request with variants and selection strategy + * @return ordered personalization result for the visitor */ PersonalizationResult personalizeList(Profile profile, Session session, PersonalizationRequest personalizationRequest); @@ -69,34 +71,74 @@ class PersonalizationRequest { private Map strategyOptions; private List contents; + /** + * Request identifier. + * + * @return request id + */ public String getId() { return id; } + /** + * Sets the request identifier. + * + * @param id request id + */ public void setId(String id) { this.id = id; } + /** + * Personalization strategy name (for example {@code alwaysSet}). + * + * @return strategy name + */ public String getStrategy() { return strategy; } + /** + * Sets the personalization strategy name. + * + * @param strategy strategy name (for example {@code alwaysSet}) + */ public void setStrategy(String strategy) { this.strategy = strategy; } + /** + * Content variants included in this request. + * + * @return personalized content items + */ public List getContents() { return contents; } + /** + * Sets the content variants for this request. + * + * @param contents personalized content items + */ public void setContents(List contents) { this.contents = contents; } + /** + * Strategy-specific options passed to the personalization engine. + * + * @return strategy options, or {@code null} if none + */ public Map getStrategyOptions() { return strategyOptions; } + /** + * Sets strategy-specific options for this request. + * + * @param strategyOptions strategy options map + */ public void setStrategyOptions(Map strategyOptions) { this.strategyOptions = strategyOptions; } @@ -111,18 +153,18 @@ class PersonalizedContent { private Map properties; /** - * Retrieves the filter identifier associated with this content filtering definition. + * Content variant identifier. * - * @return the filter identifier associated with this content filtering definition + * @return content id */ public String getId() { return id; } /** - * Sets the filter identifier associated with this content filtering definition. + * Sets the content variant identifier. * - * @param id the filter identifier associated with this content filtering definition + * @param id content id */ public void setId(String id) { this.id = id; @@ -130,7 +172,6 @@ public void setId(String id) { /** * Sets the filter identifier associated with this content filtering definition. - * * @param filterid the filter identifier associated with this content filtering definition * @deprecated As of version 1.3.0-incubating, please use {@link #setId(String)} instead */ @@ -140,27 +181,37 @@ public void setFilterid(String filterid) { } /** - * Retrieves the filters. + * Filters applied to this content variant. * - * @return the filters + * @return filter definitions */ public List getFilters() { return filters; } /** - * Sets the filters. + * Sets the filters for this content variant. * - * @param filters the filters + * @param filters filter definitions */ public void setFilters(List filters) { this.filters = filters; } + /** + * Additional properties attached to this content variant. + * + * @return content properties + */ public Map getProperties() { return properties; } + /** + * Sets additional properties for this content variant. + * + * @param properties content properties + */ public void setProperties(Map properties) { this.properties = properties; } @@ -175,45 +226,55 @@ class Filter { private Map properties; /** - * Retrieves the list of targets this filter applies on. + * Targets this filter should be evaluated against. * - * @return the applies on + * @return applicable targets */ public List getAppliesOn() { return appliesOn; } /** - * Specifies which targets this filter applies on. + * Sets the targets this filter applies to. * - * @param appliesOn the list of {@link Target} this filter should be applied on + * @param appliesOn applicable targets */ public void setAppliesOn(List appliesOn) { this.appliesOn = appliesOn; } /** - * Retrieves the condition associated with this filter. + * Condition evaluated when applying this filter. * - * @return the condition associated with this filter + * @return filter condition */ public Condition getCondition() { return condition; } /** - * Sets the condition associated with this filter. + * Sets the condition for this filter. * - * @param condition the condition associated with this filter + * @param condition filter condition */ public void setCondition(Condition condition) { this.condition = condition; } + /** + * Additional properties for this filter. + * + * @return filter properties map + */ public Map getProperties() { return properties; } + /** + * Sets additional properties for this filter. + * + * @param properties filter properties map + */ public void setProperties(Map properties) { this.properties = properties; } @@ -227,36 +288,36 @@ class Target { private List values; /** - * Retrieves the target. + * Target dimension name (for example profile property or segment). * - * @return the target + * @return target name */ public String getTarget() { return target; } /** - * Sets the target. + * Sets the target dimension name. * - * @param target the target + * @param target target name */ public void setTarget(String target) { this.target = target; } /** - * Retrieves the values. + * Allowed values for this target dimension. * - * @return the values + * @return target values */ public List getValues() { return values; } /** - * Sets the values. + * Sets allowed values for this target dimension. * - * @param values the values + * @param values target values */ public void setValues(List values) { this.values = values; diff --git a/api/src/main/java/org/apache/unomi/api/services/PrivacyService.java b/api/src/main/java/org/apache/unomi/api/services/PrivacyService.java index 3f7d5554b..98fbeb074 100644 --- a/api/src/main/java/org/apache/unomi/api/services/PrivacyService.java +++ b/api/src/main/java/org/apache/unomi/api/services/PrivacyService.java @@ -23,137 +23,134 @@ import java.util.List; /** - * This service regroups all privacy-related operations + * Entry point for consent, anonymization, and privacy-related operations. + * Wraps profile updates required for GDPR-style requests and consent tracking. */ public interface PrivacyService { /** - * Retrieves the default base Apache Unomi server information, including the name and version of the server, build - * time information and the event types - * if recognizes as well as the capabilities supported by the system. For more detailed information about the system - * and extensions use the getServerInfos method. - * @return a ServerInfo object with all the server information + * Returns base server information (name, version, build time, event types, capabilities). + * For extension details, use {@link #getServerInfos()}. + * + * @return default server information */ ServerInfo getServerInfo(); /** - * Retrieves the list of the server information objects, that include extensions. Each object includes the - * name and version of the server, build time information and the event types - * if recognizes as well as the capabilities supported by the system. - * @return a list of ServerInfo objects with all the server information + * Returns server information for the core instance and all extensions. + * + * @return server information entries, including extensions */ List getServerInfos(); /** - * Deletes the current profile (but has no effect on sessions and events). This will delete the - * persisted profile and replace it with a new empty one with the same profileId. - * @param profileId the identifier of the profile to delete and replace - * @return true if the deletion was successful + * Replaces a profile with a new empty profile that keeps the same id. + * Sessions and events are not removed. + * + * @param profileId profile identifier + * @return {@code true} if deletion succeeded */ Boolean deleteProfile(String profileId); /** - * This method will "anonymize" a profile by removing from the associated profile all the properties - * that have been defined as "denied properties". - * @param profileId the identifier of the profile that needs to be anonymized. - * @param scope The scope will be used to send events, once for the anonymizeProfile event, the other for the profileUpdated event - * @return true if the profile had some properties purged, false otherwise + * Removes denied (personally identifying) properties from a profile. + * + * @param profileId profile identifier + * @param scope scope used when emitting anonymize and profile-updated events + * @return {@code true} if any properties were removed */ Boolean anonymizeProfile(String profileId, String scope); /** - * This method will anonymize browsing data by creating an anonymous profile for the current profile, - * and then re-associating all the profile's sessions and events with the new anonymous profile - * todo this method does not anonymize any session or event properties that may contain profile - * data (such as the login event) - * @param profileId the identifier of the profile on which to perform the anonymizations of the browsing - * data - * @return true if the operation was successful, false otherwise + * Moves a profile's sessions and events to a new anonymous profile. + * Does not anonymize session or event properties that may contain PII. + * + * @param profileId profile identifier + * @return {@code true} if the operation succeeded */ Boolean anonymizeBrowsingData(String profileId); /** - * This method will perform two operations, first it will call the anonymizeBrowsingData method on the - * specified profile, and then it will delete the profile from the persistence service. - * @param profileId the identifier of the profile - * @param purgeData flag that indicates whether to purge the profile's data - * @return true if the operation was successful, false otherwise + * Anonymizes browsing data, then optionally deletes the original profile. + * + * @param profileId profile identifier + * @param purgeData when {@code true}, deletes the profile after anonymization + * @return {@code true} if the operation succeeded */ Boolean deleteProfileData(String profileId,boolean purgeData); /** - * Controls the activation/deactivation of anonymous browsing. This method will simply set a system - * property called requireAnonymousProfile that will be then use to know if we should associate - * browsing data with the main profile or the associated anonymous profile. - * Note that changing this setting will also reset the goals and pastEvents system properties for the - * profile. - * @param profileId the identifier of the profile on which to set the anonymous browsing property flag - * @param anonymous the value of the anonymous browsing flag. - * @param scope a scope used to send a profileUpdated event internally - * @return true if successful, false otherwise + * Enables or disables anonymous browsing for a profile. + * Resets goals and past-events system properties when the flag changes. + * + * @param profileId profile identifier + * @param anonymous anonymous-browsing flag value + * @param scope scope used when emitting a profile-updated event + * @return {@code true} if the update succeeded */ Boolean setRequireAnonymousBrowsing(String profileId, boolean anonymous, String scope); /** - * Tests if the anonymous browsing flag is set of the specified profile. - * @param profileId the identifier of the profile on which we want to retrieve the anonymous browsing flag - * @return true if successful, false otherwise + * Checks whether anonymous browsing is required for the given profile id. + * + * @param profileId profile identifier + * @return {@code true} if anonymous browsing is required */ Boolean isRequireAnonymousBrowsing(String profileId); /** - * Tests if the anonymous browsing flag is set of the specified profile. - * @param profile the profile on which we want to retrieve the anonymous browsing flag - * @return true if successful, false otherwise + * Checks whether anonymous browsing is required for the given profile. + * + * @param profile profile to inspect + * @return {@code true} if anonymous browsing is required */ Boolean isRequireAnonymousBrowsing(Profile profile); /** - * Build a new anonymous profile (but doesn't persist it in the persistence service). This will also - * copy the profile properties from the passed profile that are not listed as denied properties. - * @param profile the profile for which to create the anonymous profile - * @return a newly created (but not persisted) profile for the passed profile. + * Builds an anonymous profile copy without persisting it. + * Copies non-denied properties from the source profile. + * + * @param profile source profile + * @return new anonymous profile (not persisted) */ Profile getAnonymousProfile(Profile profile); /** - * Retrieve the list of events that the profile has deactivated. For each profile a visitor may indicate - * that he doesn't want some events to be collected. This method retrieves this list from the specified - * profile - * @param profileId the identifier for the profile for which we want to retrieve the list of forbidden - * event types - * @return a list of event types + * Returns event types the visitor has opted out of collecting. + * + * @param profileId profile identifier + * @return blocked event type names */ List getFilteredEventTypes(String profileId); /** - * Retrieve the list of events that the profile has deactivated. For each profile a visitor may indicate - * that he doesn't want some events to be collected. This method retrieves this list from the specified - * profile - * @param profile the profile for which we want to retrieve the list of forbidden - * event types - * @return a list of event types + * Returns event types the visitor has opted out of collecting. + * + * @param profile profile to inspect + * @return blocked event type names */ List getFilteredEventTypes(Profile profile); /** - * Set the list of filtered event types for a profile. This is the list of event types that the visitor - * has specified he does not want the server to collect. - * @param profileId the identifier of the profile on which to filter the events - * @param eventTypes a list of event types that will be filter for the profile - * @return true if successfull, false otherwise. + * Sets event types the visitor has opted out of collecting. + * + * @param profileId profile identifier + * @param eventTypes blocked event type names + * @return {@code true} if the update succeeded */ Boolean setFilteredEventTypes(String profileId, List eventTypes); /** - * Gets the list of denied properties. These are properties marked with a personal identifier tag. - * @param profileId the identified of the profile - * @return a list of profile properties identifiers that are marked as personally identifying + * Returns property names tagged as personally identifying for the profile. + * + * @param profileId profile identifier + * @return denied property identifiers */ List getDeniedProperties(String profileId); /** * Sets the list of denied properties. + * * @param profileId the profile for which to see the denied properties * @param propertyNames the property names to be denied * @return null all the time, this method is not used and is marked as deprecated @@ -165,6 +162,7 @@ public interface PrivacyService { /** * This method doesn't do anything anymore please don't use it + * * @param profileId the identifier of the profile * @return do not use * @deprecated As of version 1.3.0-incubating, do not use this method @@ -174,6 +172,7 @@ public interface PrivacyService { /** * This method doesn't do anything anymore please don't use it + * * @param profileId the identifier of the profile * @param propertyNames do not use * @return do not use @@ -183,10 +182,11 @@ public interface PrivacyService { Boolean setDeniedPropertyDistribution(String profileId, List propertyNames); /** - * Removes a property from the specified profile. This change is persisted. - * @param profileId the identifier of the profile - * @param propertyName the name of the property to remove - * @return true if sucessfull, false otherwise + * Removes a property from a profile and persists the change. + * + * @param profileId profile identifier + * @param propertyName property to remove + * @return {@code true} if the removal succeeded */ Boolean removeProperty(String profileId, String propertyName); 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 78ecb1ffb..0c1041415 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 @@ -25,431 +25,401 @@ import java.util.*; /** - * A service to access and operate on {@link Profile}s, {@link Session}s and {@link Persona}s. + * Primary API for {@link Profile}s, {@link Session}s, and {@link Persona}s. + * Loads and saves visitor data, merges profiles, manages sessions, and + * supports persona-based testing workflows. */ public interface ProfileService { + /** + * The system tag name used to identify property types that store + * personal identifiers. + */ String PERSONAL_IDENTIFIER_TAG_NAME = "personalIdentifierProperties"; /** - * Retrieves the number of unique profiles. + * Returns the total number of profiles. * - * @return the number of unique profiles. + * @return profile count */ long getAllProfilesCount(); /** - * Retrieves profiles or personas matching the specified query. + * Searches profiles or personas using a structured query. * - * @param the specific sub-type of {@link Profile} to retrieve - * @param query a {@link Query} specifying which elements to retrieve - * @param clazz the class of elements to retrieve - * @return a {@link PartialList} of {@code T} instances matching the specified query + * @param profile subtype to return + * @param query search query + * @param clazz profile class to load + * @return matching profiles or personas */ PartialList search(Query query, Class clazz); /** - * Retrieves sessions matching the specified query. + * Searches sessions using a structured query. * - * @param query a {@link Query} specifying which elements to retrieve - * @return a {@link PartialList} of sessions matching the specified query + * @param query search query + * @return matching sessions */ PartialList searchSessions(Query query); /** - * Creates a String containing comma-separated values (CSV) formatted version of profiles matching the specified query. + * Exports matching profile properties as a CSV string. * - * @param query the query specifying which profiles to export - * @return a CSV-formatted String version of the profiles matching the specified query + * @param query search query selecting profiles to export + * @return CSV representation of matching profile properties */ String exportProfilesPropertiesToCsv(Query query); /** - * Find profiles which have the specified property with the specified value, ordered according to the specified {@code sortBy} String and paged: only - * {@code size} of them are retrieved, starting with the {@code offset}-th one. + * Finds profiles with a given property value, ordered and paged. * - * TODO: replace with version using a query instead of separate parameters - * TODO: remove as it's unused? + * Prefer {@link #search(Query, Class)} for new code; this overload remains for backward compatibility. * - * @param propertyName the name of the property we're interested in - * @param propertyValue the value of the property we want profiles to have - * @param offset zero or a positive integer specifying the position of the first profile in the total ordered collection of matching profiles - * @param size a positive integer specifying how many matching profiles should be retrieved or {@code -1} if all of them should be retrieved - * @param sortBy an optional ({@code null} if no sorting is required) String of comma ({@code ,}) separated property names on which ordering should be performed, ordering elements according to the property order in - * the String, considering each in turn and moving on to the next one in case of equality of all preceding ones. Each property name is optionally - * followed by a column ({@code :}) and an order specifier: {@code asc} or {@code desc}. - * @return a {@link PartialList} of matching profiles + * @param propertyName property name to match + * @param propertyValue required property value + * @param offset zero-based index of the first result + * @param size maximum results to return, or {@code -1} for all + * @param sortBy optional comma-separated property list with optional {@code :asc}/{@code :desc} suffixes + * @return matching profiles */ PartialList findProfilesByPropertyValue(String propertyName, String propertyValue, int offset, int size, String sortBy); /** - * Merges the specified profiles into the provided so-called master profile, merging properties according to the {@link PropertyMergeStrategyType} specified on their {@link - * PropertyType}. + * Merges profiles into a master profile using each property's merge strategy. * - * @param masterProfile the profile into which the specified profiles will be merged - * @param profilesToMerge the list of profiles to merge into the specified master profile - * @return the merged profile + * @param masterProfile profile that receives merged data + * @param profilesToMerge profiles to merge into the master + * @return merged master profile */ Profile mergeProfiles(Profile masterProfile, List profilesToMerge); /** - * Retrieves the profile identified by the specified identifier. + * Loads a profile by id. * - * @param profileId the identifier of the profile to retrieve - * @return the profile identified by the specified identifier or {@code null} if no such profile exists + * @param profileId profile identifier + * @return matching profile, or {@code null} if none exists */ Profile load(String profileId); /** - * Saves the specified profile in the context server. + * Persists a profile. * - * @param profile the profile to be saved - * @return the newly saved profile + * @param profile profile to save + * @return saved profile */ Profile save(Profile profile); /** - * Adds the alias to the profile. + * Links an alias to a profile for the given client. * - * @param profileID the identifier of the profile - * @param alias the alias which should be linked with of the profile - * @param clientID the identifier of the client + * @param profileID profile identifier + * @param alias alias to link + * @param clientID client identifier */ void addAliasToProfile(String profileID, String alias, String clientID); /** - * Removes the alias from the profile. + * Unlinks an alias from a profile for the given client. * - * @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 + * @param profileID profile identifier + * @param alias alias to unlink + * @param clientID client identifier + * @return removed alias, or {@code null} if not found */ ProfileAlias removeAliasFromProfile(String profileID, String alias, String clientID); /** - * Find profile aliases which have the specified property with the specified value, ordered according to the specified {@code sortBy} String and paged: only - * {@code size} of them are retrieved, starting with the {@code offset}-th one. + * Lists aliases linked to a profile, ordered and paged. * - * @param profileId the identifier of the profile - * @param offset zero or a positive integer specifying the position of the first profile in the total ordered collection of matching profiles - * @param size a positive integer specifying how many matching profiles should be retrieved or {@code -1} if all of them should be retrieved - * @param sortBy an optional ({@code null} if no sorting is required) String of comma ({@code ,}) separated property names on which ordering should be performed, ordering elements according to the property order in - * the String, considering each in turn and moving on to the next one in case of equality of all preceding ones. Each property name is optionally - * followed by a column ({@code :}) and an order specifier: {@code asc} or {@code desc}. - * @return a {@link PartialList} of matching profiles + * @param profileId profile identifier + * @param offset zero-based index of the first result + * @param size maximum results to return, or {@code -1} for all + * @param sortBy optional comma-separated property list with optional {@code :asc}/{@code :desc} suffixes + * @return matching profile aliases */ PartialList findProfileAliases(String profileId, int offset, int size, String sortBy); /** - * Merge the specified profile properties in an existing profile,or save new profile if it does not exist yet + * Saves a new profile or merges properties into an existing one. * - * @param profile the profile to be saved - * @return the newly saved or merged profile or null if the save or merge operation failed. + * @param profile profile to save or merge + * @return saved or merged profile, or {@code null} on failure */ Profile saveOrMerge(Profile profile); /** - * Removes the profile (or persona if the {@code persona} parameter is set to {@code true}) identified by the specified identifier. + * Deletes a profile or persona by id. * - * @param profileId the identifier of the profile or persona to delete - * @param persona {@code true} if the specified identifier is supposed to refer to a persona, {@code false} if it is supposed to refer to a profile + * @param profileId profile or persona identifier + * @param persona {@code true} when deleting a persona, {@code false} for a profile */ void delete(String profileId, boolean persona); /** - * Retrieves the sessions associated with the profile identified by the specified identifier that match the specified query (if specified), ordered according to the specified - * {@code sortBy} String and and paged: only {@code size} of them are retrieved, starting with the {@code offset}-th one. - * - * TODO: use a Query object instead of distinct parameter + * Lists a profile's sessions with optional full-text filtering, ordered and paged. * - * @param profileId the identifier of the profile we want to retrieve sessions from - * @param query a String of text used for fulltext filtering which sessions we are interested in or {@code null} (or an empty String) if we want to retrieve all sessions - * @param offset zero or a positive integer specifying the position of the first session in the total ordered collection of matching sessions - * @param size a positive integer specifying how many matching sessions should be retrieved or {@code -1} if all of them should be retrieved - * @param sortBy an optional ({@code null} if no sorting is required) String of comma ({@code ,}) separated property names on which ordering should be performed, ordering elements according to the property order in the - * String, considering each in turn and moving on to the next one in case of equality of all preceding ones. Each property name is optionally followed by - * a column ({@code :}) and an order specifier: {@code asc} or {@code desc}. - * @return a {@link PartialList} of matching sessions + * @param profileId profile identifier + * @param query optional full-text filter, or {@code null} for all sessions + * @param offset zero-based index of the first result + * @param size maximum results to return, or {@code -1} for all + * @param sortBy optional comma-separated property list with optional {@code :asc}/{@code :desc} suffixes + * @return matching sessions */ PartialList getProfileSessions(String profileId, String query, int offset, int size, String sortBy); /** - * Retrieves the session identified by the specified identifier. - * @deprecated {@code dateHint} is not supported anymore, please use {@link #loadSession(String)} + * Loads a session by id. * - * @param sessionId the identifier of the session to be retrieved - * @param dateHint a Date helping in identifying where the item is located - * @return the session identified by the specified identifier + * @deprecated {@code dateHint} is not supported anymore; use {@link #loadSession(String)} + * @param sessionId session identifier + * @param dateHint unused date hint + * @return matching session */ @Deprecated Session loadSession(String sessionId, Date dateHint); /** - * Retrieves the session identified by the specified identifier. + * Loads a session by id. * - * @param sessionId the identifier of the session to be retrieved - * @return the session identified by the specified identifier + * @param sessionId session identifier + * @return matching session */ default Session loadSession(String sessionId) { return loadSession(sessionId, null); }; /** - * Saves the specified session. + * Persists a session. * - * @param session the session to be saved - * @return the newly saved session + * @param session session to save + * @return saved session */ Session saveSession(Session session); /** - * Retrieves sessions associated with the profile identified by the specified identifier. + * Returns all sessions linked to a profile. * - * @param profileId the profile id for which we want to retrieve the sessions - * @return a {@link PartialList} of the profile's sessions + * @param profileId profile identifier + * @return profile sessions */ PartialList findProfileSessions(String profileId); /** - * Removes all sessions of the specified profile + * Deletes all sessions belonging to a profile. * - * @param profileId identifier of the profile that we want to remove it's sessions + * @param profileId profile identifier */ void removeProfileSessions(String profileId); /** - * Deletes the session identified by the given identifier from persistence. - * Note: events belonging to this session are NOT removed; they remain in persistence - * with a dangling sessionId reference. + * Deletes a session by id. + * Events for the session remain in persistence with a dangling session id. * - * @param sessionIdentifier the unique identifier for the session + * @param sessionIdentifier session identifier */ void deleteSession(String sessionIdentifier); /** - * Checks whether the specified profile and/or session satisfy the specified condition. + * Evaluates whether a profile and/or session satisfy a condition. * - * @param condition the condition we're testing against which might or might not have profile- or session-specific sub-conditions - * @param profile the profile we're testing - * @param session the session we're testing - * @return {@code true} if the profile and/or sessions match the specified condition, {@code false} otherwise + * @param condition condition to test (may include profile- or session-specific parts) + * @param profile profile to evaluate + * @param session session to evaluate + * @return {@code true} when the condition matches */ boolean matchCondition(Condition condition, Profile profile, Session session); /** - * Update all profiles in batch according to the specified {@link BatchUpdate} + * Applies a batch update to matching profiles. * - * @param update the batch update specification + * @param update batch update specification */ void batchProfilesUpdate(BatchUpdate update); /** - * Retrieves the persona identified by the specified identifier. + * Loads a persona by id. * - * @param personaId the identifier of the persona to retrieve - * @return the persona associated with the specified identifier or {@code null} if no such persona exists. + * @param personaId persona identifier + * @return matching persona, or {@code null} if none exists */ Persona loadPersona(String personaId); /** - * Persists the specified {@link Persona} in the context server. + * Persists a persona. * - * @param persona the persona to persist - * @return the newly persisted persona + * @param persona persona to save + * @return saved persona */ Persona savePersona(Persona persona); /** - * Retrieves the persona identified by the specified identifier and all its associated sessions + * Loads a persona and all of its sessions. * - * @param personaId the identifier of the persona to retrieve - * @return a {@link PersonaWithSessions} instance with the persona identified by the specified identifier and all its associated sessions + * @param personaId persona identifier + * @return persona with associated sessions */ PersonaWithSessions loadPersonaWithSessions(String personaId); /** - * Creates a persona with the specified identifier and automatically creates an associated session with it. + * Creates a persona and an initial session for it. * - * @param personaId the identifier to use for the new persona - * @return the newly created persona + * @param personaId identifier for the new persona + * @return newly created persona */ Persona createPersona(String personaId); /** - * Retrieves the sessions associated with the persona identified by the specified identifier, ordered according to the specified {@code sortBy} String and and paged: only - * {@code size} of them are retrieved, starting with the {@code offset}-th one. + * Lists persona sessions, ordered and paged. * - * @param personaId the persona id - * @param offset zero or a positive integer specifying the position of the first session in the total ordered collection of matching sessions - * @param size a positive integer specifying how many matching sessions should be retrieved or {@code -1} if all of them should be retrieved - * @param sortBy an optional ({@code null} if no sorting is required) String of comma ({@code ,}) separated property names on which ordering should be performed, ordering elements according to the property order in the - * String, considering each in turn and moving on to the next one in case of equality of all preceding ones. Each property name is optionally followed by - * a column ({@code :}) and an order specifier: {@code asc} or {@code desc}. - * @return a {@link PartialList} of sessions for the persona identified by the specified identifier + * @param personaId persona identifier + * @param offset zero-based index of the first result + * @param size maximum results to return, or {@code -1} for all + * @param sortBy optional comma-separated property list with optional {@code :asc}/{@code :desc} suffixes + * @return matching persona sessions */ PartialList getPersonaSessions(String personaId, int offset, int size, String sortBy); /** - * Save a persona with its sessions. + * Persists a persona together with its sessions. * - * @param personaToSave the persona object containing all the persona information and sessions - * @return the persona with sessions + * @param personaToSave persona and session data to save + * @return saved persona with sessions */ PersonaWithSessions savePersonaWithSessions(PersonaWithSessions personaToSave); - /** - * Retrieves all the property types associated with the specified target. - * - * TODO: move to a different class + * Returns property types registered for the given target. * - * @param target the target for which we want to retrieve the associated property types - * @return a collection of all the property types associated with the specified target + * @param target target name (for example profiles or events) + * @return property types for the target */ Collection getTargetPropertyTypes(String target); /** - * Retrieves all known property types. + * Returns all property types grouped by target. * - * TODO: move to a different class - * TODO: use Map instead of HashMap - * - * @return a Map associating targets as keys to related {@link PropertyType}s + * @return map of target name to property types */ Map> getTargetPropertyTypes(); /** - * Retrieves all property types with the specified tag - * - * TODO: move to a different class + * Returns property types tagged with the given tag. * - * @param tag the tag name marking property types we want to retrieve - * @return a Set of the property types with the specified tag + * @param tag tag name + * @return matching property types */ Set getPropertyTypeByTag(String tag); /** - * Retrieves all property types with the specified system tag + * Returns property types with the given system tag. * - * TODO: move to a different class - * - * @param tag the system tag name marking property types we want to retrieve - * @return a Set of the property types with the specified system tag + * @param tag system tag name + * @return matching property types */ Set getPropertyTypeBySystemTag(String tag); /** - * TODO - * @param fromPropertyTypeId fromPropertyTypeId - * @return property type mapping + * Returns the persistence mapping for a property type. + * + * @param fromPropertyTypeId source property type id + * @return mapped property type id */ String getPropertyTypeMapping(String fromPropertyTypeId); /** - * TODO - * @param propertyName the property name - * @return list of property types + * Returns property types mapped to the given property name. + * + * @param propertyName property name to look up + * @return matching property types */ Collection getPropertyTypeByMapping(String propertyName); /** - * Retrieves the property type identified by the specified identifier. - * - * TODO: move to a different class + * Looks up a property type by id. * - * @param id the identifier of the property type to retrieve - * @return the property type identified by the specified identifier or {@code null} if no such property type exists + * @param id property type identifier + * @return matching property type, or {@code null} if none exists */ PropertyType getPropertyType(String id); /** - * Persists the specified property type in the context server. - * - * TODO: move to a different class + * Registers or updates a property type definition. * - * @param property the property type to persist - * @return {@code true} if the property type was properly created, {@code false} otherwise (for example, if the property type already existed + * @param property property type to persist + * @return {@code true} when created, {@code false} when it already existed */ boolean setPropertyType(PropertyType property); /** - * This function will try to set the target on the property type if not set already, based on the file URL + * Infers and sets the property type target from a definition URL when missing. + * By default uses the fifth path segment after {@code /}. * - * @param predefinedPropertyTypeURL the URL to extract the target from if the target is not yet. By default it will - * use the 5's part after a "/" character - * @param propertyType the property type to register + * @param predefinedPropertyTypeURL URL of the property type definition + * @param propertyType property type to update */ void setPropertyTypeTarget(URL predefinedPropertyTypeURL, PropertyType propertyType); /** - * Deletes the property type identified by the specified identifier. + * Deletes a property type by id. * - * TODO: move to a different class - * - * @param propertyId the identifier of the property type to delete - * @return {@code true} if the property type was properly deleted, {@code false} otherwise + * @param propertyId property type identifier + * @return {@code true} when deleted successfully */ boolean deletePropertyType(String propertyId); /** - * Retrieves the existing property types for the specified type as defined by the Item subclass public field {@code ITEM_TYPE} and with the specified tag. - * - * TODO: move to a different class + * Returns property types defined for an item type that carry the given tag. * - * @param tag the tag we're interested in - * @param itemType the String representation of the item type we want to retrieve the count of, as defined by its class' {@code ITEM_TYPE} field - * @return all property types defined for the specified item type and with the specified tag + * @param tag tag to match + * @param itemType item type name from the item class {@code ITEM_TYPE} field + * @return matching property types */ Set getExistingProperties(String tag, String itemType); /** - * Retrieves the existing property types for the specified type as defined by the Item subclass public - * field {@code ITEM_TYPE} and with the specified tag (system or regular) + * Returns property types defined for an item type that carry the given tag or system tag. * - * TODO: move to a different class - * - * @param tag the tag we're interested in - * @param itemType the String representation of the item type we want to retrieve the count of, as defined by its class' {@code ITEM_TYPE} field - * @param systemTag whether the specified is a system tag or a regular one - * @return all property types defined for the specified item type and with the specified tag + * @param tag tag to match + * @param itemType item type name from the item class {@code ITEM_TYPE} field + * @param systemTag {@code true} when {@code tag} is a system tag + * @return matching property types */ Set getExistingProperties(String tag, String itemType, boolean systemTag); /** - * Forces a refresh of the profile service, to load data from persistence immediately instead of waiting for - * scheduled tasks to execute. Warning : this may have serious impacts on performance so it should only be used - * in specific scenarios such as integration tests. + * Reloads profile service state from persistence immediately. + * Expensive; intended for integration tests and similar scenarios. */ void refresh(); /** - * Purge (delete) profiles - * example: Purge profile inactive since 10 days only: - * purgeProfiles(10, 0); - * example: Purge profile created since 30 days only: - * purgeProfiles(0, 30); + * Deletes profiles by inactivity and/or age criteria. + * Example: purge profiles inactive for 10 days only: {@code purgeProfiles(10, 0)}. + * Example: purge profiles created within the last 30 days: {@code purgeProfiles(0, 30)}. * - * @param inactiveNumberOfDays will purge profiles with no visits since this number of days (0 or negative value, will have no effect) - * @param existsNumberOfDays will purge profiles created since this number of days (0 or negative value, will have no effect) + * @param inactiveNumberOfDays purge profiles with no visits since this many days (ignored when {@code <= 0}) + * @param existsNumberOfDays purge profiles created within this many days (ignored when {@code <= 0}) */ void purgeProfiles(int inactiveNumberOfDays, int existsNumberOfDays); /** - * Purge (delete) session items - * @param existsNumberOfDays will purge sessions created since this number of days (0 or negative value, will have no effect) + * Deletes session items older than the given age threshold. + * + * @param existsNumberOfDays purge sessions created within this many days (ignored when {@code <= 0}) */ void purgeSessionItems(int existsNumberOfDays); /** - * Purge (delete) event items - * @param existsNumberOfDays will purge events created since this number of days (0 or negative value, will have no effect) + * Deletes event items older than the given age threshold. + * + * @param existsNumberOfDays purge events created within this many days (ignored when {@code <= 0}) */ void purgeEventItems(int existsNumberOfDays); /** - * Use purgeSessionItems and purgeEventItems to remove rollover items instead - * @param existsNumberOfMonths used to remove monthly indices older than this number of months + * @deprecated Use {@link #purgeSessionItems(int)} and {@link #purgeEventItems(int)} for rollover cleanup. + * + * @param existsNumberOfMonths remove monthly indices older than this many months */ @Deprecated void purgeMonthlyItems(int existsNumberOfMonths); diff --git a/api/src/main/java/org/apache/unomi/api/services/QueryService.java b/api/src/main/java/org/apache/unomi/api/services/QueryService.java index c397fda75..b4b657825 100644 --- a/api/src/main/java/org/apache/unomi/api/services/QueryService.java +++ b/api/src/main/java/org/apache/unomi/api/services/QueryService.java @@ -24,32 +24,29 @@ import java.util.Map; /** - * A service to perform queries. + * Runs stored queries and aggregations against the persistence layer. + * Complements segment search with lower-level query and aggregate access. */ public interface QueryService { /** - * Retrieves the number of items with the specified type as defined by the Item subclass public field {@code ITEM_TYPE} and aggregated by possible values of the specified - * property. + * Counts items of the given type grouped by a property's distinct values. * - * @param itemType the String representation of the item type we want to retrieve the count of, as defined by its class' {@code ITEM_TYPE} field - * @param property the property we're aggregating on, i.e. for each possible value of this property, we are counting how many items of the specified type have that value - * @return a Map associating a specific value of the property to the cardinality of items with that value + * @param itemType item type name from the item class {@code ITEM_TYPE} field + * @param property property to aggregate on + * @return map of property value to item count * @see Item Item for a discussion of {@code ITEM_TYPE} */ Map getAggregate(String itemType, String property); /** - * TODO: rework, this method is confusing since it either behaves like {@link #getAggregate(String, String)} if query is null but completely differently if it isn't + * Counts items of the given type grouped by property values, optionally filtered by an aggregate query. + * Also returns the global document count when {@code query} is {@code null}. * - * Retrieves the number of items with the specified type as defined by the Item subclass public field {@code ITEM_TYPE} and aggregated by possible values of the specified - * property or, if the specified query is not {@code null}, perform that aggregate query. - * Also return the global count of document matching the {@code ITEM_TYPE} - * - * @param itemType the String representation of the item type we want to retrieve the count of, as defined by its class' {@code ITEM_TYPE} field - * @param property the property we're aggregating on, i.e. for each possible value of this property, we are counting how many items of the specified type have that value - * @param query the {@link AggregateQuery} specifying the aggregation that should be perfomed - * @return a Map associating a specific value of the property to the cardinality of items with that value + * @param itemType item type name from the item class {@code ITEM_TYPE} field + * @param property property to aggregate on + * @param query optional aggregate query, or {@code null} for simple aggregation + * @return map of property value to item count * @see Item Item for a discussion of {@code ITEM_TYPE} * @deprecated As of 1.3.0-incubating, please use {@link #getAggregateWithOptimizedQuery(String, String, AggregateQuery)} instead */ @@ -57,38 +54,35 @@ public interface QueryService { Map getAggregate(String itemType, String property, AggregateQuery query); /** - * Retrieves the number of items with the specified type as defined by the Item subclass public field {@code ITEM_TYPE} and aggregated by possible values of the specified - * property or, if the specified query is not {@code null}, perform that aggregate query. - * This aggregate won't return the global count and should therefore be much faster than {@link #getAggregate(String, String, AggregateQuery)} + * Counts items of the given type grouped by property values using an optimized aggregate query. + * Does not return a global document count. * - * @param itemType the String representation of the item type we want to retrieve the count of, as defined by its class' {@code ITEM_TYPE} field - * @param property the property we're aggregating on, i.e. for each possible value of this property, we are counting how many items of the specified type have that value - * @param query the {@link AggregateQuery} specifying the aggregation that should be perfomed - * @return a Map associating a specific value of the property to the cardinality of items with that value + * @param itemType item type name from the item class {@code ITEM_TYPE} field + * @param property property to aggregate on + * @param query aggregate query defining the aggregation + * @return map of property value to item count * @see Item Item for a discussion of {@code ITEM_TYPE} */ Map getAggregateWithOptimizedQuery(String itemType, String property, AggregateQuery query); /** - * Retrieves the number of items of the specified type as defined by the Item subclass public field {@code ITEM_TYPE} and matching the specified {@link Condition}. + * Counts items of the given type that match a condition. * - * @param condition the condition the items must satisfy - * @param itemType the String representation of the item type we want to retrieve the count of, as defined by its class' {@code ITEM_TYPE} field - * @return the number of items of the specified type + * @param condition filter condition + * @param itemType item type name from the item class {@code ITEM_TYPE} field + * @return matching item count * @see Item Item for a discussion of {@code ITEM_TYPE} */ long getQueryCount(String itemType, Condition condition); /** - * Retrieves the specified metrics for the specified field of items of the specified type as defined by the Item subclass public field {@code ITEM_TYPE} and matching the - * specified {@link Condition}. + * Computes numeric metrics (sum, avg, min, max) for a field on items matching a condition. * - * @param condition the condition the items must satisfy - * @param slashConcatenatedMetrics a String specifying which metrics should be computed, separated by a slash ({@code /}) (possible values: {@code sum} for the sum of the - * values, {@code avg} for the average of the values, {@code min} for the minimum value and {@code max} for the maximum value) - * @param property the name of the field for which the metrics should be computed - * @param type the String representation of the item type we want to retrieve the count of, as defined by its class' {@code ITEM_TYPE} field - * @return a Map associating computed metric name as key to its associated value + * @param condition filter condition + * @param slashConcatenatedMetrics metrics to compute, separated by {@code /} ({@code sum}, {@code avg}, {@code min}, {@code max}) + * @param property field name to aggregate + * @param type item type name from the item class {@code ITEM_TYPE} field + * @return map of metric name to computed value * @see Item Item for a discussion of {@code ITEM_TYPE} */ Map getMetric(String type, String property, String slashConcatenatedMetrics, Condition condition); diff --git a/api/src/main/java/org/apache/unomi/api/services/RuleListenerService.java b/api/src/main/java/org/apache/unomi/api/services/RuleListenerService.java index f65990403..0d948a1f2 100644 --- a/api/src/main/java/org/apache/unomi/api/services/RuleListenerService.java +++ b/api/src/main/java/org/apache/unomi/api/services/RuleListenerService.java @@ -21,7 +21,9 @@ import org.apache.unomi.api.rules.Rule; /** - * A service that gets called when a rule's conditions are evaluated or when a rule's actions are executed. + * Observer notified when rules are evaluated or executed. + * Lets extensions audit rule firing or add diagnostics without changing + * core rule processing. */ public interface RuleListenerService { @@ -29,8 +31,11 @@ public interface RuleListenerService { * This enum indicates which type of already raised event we are dealing with */ enum AlreadyRaisedFor { + /** Rule was already raised for the session. */ SESSION, + /** Rule was already raised for the profile. */ PROFILE, + /** Rule was already raised for the event. */ EVENT } @@ -38,6 +43,7 @@ enum AlreadyRaisedFor { * Called before a rule's conditions are evaluated. Be careful when implemented this listener because rule's condition * are called in very high frequencies and the performance of this listener might have a huge impact on rule's * performance + * * @param rule the rule that is being evaluated * @param event the event we are processing and evaluating against the rule */ @@ -45,6 +51,7 @@ enum AlreadyRaisedFor { /** * Called when a rule has already been raised either for a session or a profile. + * * @param alreadyRaisedFor an enum that indicates if the rule was already raised once for the session or for the * profile * @param rule the rule that has already been raised @@ -54,6 +61,7 @@ enum AlreadyRaisedFor { /** * Called just before a matching rule's actions are about to be executed. + * * @param rule the matching rule for the current event * @param event the event we are processing that matched the current rule. */ diff --git a/api/src/main/java/org/apache/unomi/api/services/RulesService.java b/api/src/main/java/org/apache/unomi/api/services/RulesService.java index dd5a44786..7f3ad7260 100644 --- a/api/src/main/java/org/apache/unomi/api/services/RulesService.java +++ b/api/src/main/java/org/apache/unomi/api/services/RulesService.java @@ -31,100 +31,105 @@ import java.util.Set; /** - * A service to access and operate on {@link Rule}s. + * Loads, saves, and searches {@link org.apache.unomi.api.rules.Rule} definitions. + * Rules tie conditions to actions and drive most automated behavior when + * events arrive. */ public interface RulesService { /** - * Retrieves the metadata for all known rules. - * Note that it only includes the rules in memory, not those persisted in storage. - * @return the Set of known metadata + * Returns metadata for all in-memory rules. + * Does not query persistence directly. + * + * @return rule metadata from the in-memory cache */ Set getRuleMetadatas(); /** - * Retrieves rule metadatas for rules matching the specified {@link Query}. + * Returns metadata for rules matching the given query. * - * @param query the query the rules which metadata we want to retrieve must match - * @return a {@link PartialList} of rules metadata for the rules matching the specified query + * @param query filter for rules whose metadata should be returned + * @return matching rule metadata */ PartialList getRuleMetadatas(Query query); /** - * Retrieves rule details for rules matching the specified query. + * Returns full rule definitions matching the given query. * - * @param query the query specifying which rules to retrieve - * @return a {@link PartialList} of rule details for the rules matching the specified query + * @param query filter for rules to return + * @return matching rules */ PartialList getRuleDetails(Query query); /** - * Get all rules available in the system. - * (This is not doing a persistence query to retrieve the rules, it's using the internal in memory cache - * that is refreshed every second by default but can vary depending on your own configuration) + * Returns all rules from the in-memory cache. + * The cache refreshes on a configurable interval (default one second). * - * @return all rules available. + * @return all cached rules */ List getAllRules(); /** - * Retrieves the rule identified by the specified identifier. + * Loads a rule by id. * - * @param ruleId the identifier of the rule we want to retrieve - * @return the rule identified by the specified identifier or {@code null} if no such rule exists. + * @param ruleId rule identifier + * @return matching rule, or {@code null} if none exists */ Rule getRule(String ruleId); /** - * Retrieves the statistics for a rule - * @param ruleId the identifier of the rule - * @return a long representing the number of times the rule was matched and executed. + * Returns execution statistics for a rule. + * + * @param ruleId rule identifier + * @return rule match and execution counts */ RuleStatistics getRuleStatistics(String ruleId); /** - * Retrieves the statistics for all the rules - * @return a map containing rule IDs as key, and the RuleStatistics object as a value + * Returns execution statistics for all rules. + * + * @return map of rule id to statistics */ Map getAllRuleStatistics(); /** - * Resets all the rule statistics to zero, useful when testing or if you want to set a point in time. + * Resets match and execution counters for every rule. */ void resetAllRuleStatistics(); /** - * Persists the specified rule to the context server. + * Persists a rule definition. * - * @param rule the rule to be persisted + * @param rule rule to save */ void setRule(Rule rule); /** - * Deletes the rule identified by the specified identifier. + * Deletes a rule by id. * - * @param ruleId the identifier of the rule we want to delete + * @param ruleId rule identifier */ void removeRule(String ruleId); /** - * Retrieves tracked conditions (rules with a condition marked with the {@code trackedCondition} tag and which {@code sourceEventCondition} matches the specified item) for the - * specified item. + * Returns tracked conditions whose source-event condition matches the given item. + * Tracked conditions are rules tagged with {@code trackedCondition}. * - * @param item the item which tracked conditions we want to retrieve - * @return the Set of tracked conditions for the specified item + * @param item item to match against source-event conditions + * @return matching tracked conditions */ Set getTrackedConditions(Item item); /** - * Retrieves all the matching rules for a specific event - * @param event the event we want to retrieve all the matching rules for - * @return a set of rules that match the event passed in the parameters + * Returns rules whose conditions match the given event. + * + * @param event event to evaluate + * @return matching rules */ public Set getMatchingRules(Event event); /** - * Refresh the rules for this instance by reloading them from the persistence backend + * Reloads rules from persistence into the in-memory cache. */ public void refreshRules(); 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 6868ebc65..0b770b91f 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 @@ -264,6 +264,7 @@ ScheduledTask createTask(String taskType, /** * Gets the value of a specific metric. + * * @param metric The metric name * @return The current value of the metric */ @@ -276,10 +277,19 @@ ScheduledTask createTask(String taskType, /** * Gets all metrics as a map. + * * @return Map of metric names to their current values */ Map getAllMetrics(); + /** + * Gets a list of scheduled tasks matching the specified status. + * This method allows filtering all managed tasks (both persistent and + * in-memory) by their current operational state, such as RUNNING or FAILED. + * + * @param taskStatus the desired {@link ScheduledTask.TaskStatus} to filter by + * @return tasks that currently match the given status + */ List findTasksByStatus(ScheduledTask.TaskStatus taskStatus); /** @@ -290,69 +300,91 @@ ScheduledTask createTask(String taskType, interface TaskBuilder { /** * Sets task parameters. + * * @param parameters task-specific parameters + * @return this builder for method chaining */ TaskBuilder withParameters(Map parameters); /** * Sets initial execution delay. + * * @param initialDelay delay before first execution * @param timeUnit time unit for delay + * @return this builder for method chaining */ TaskBuilder withInitialDelay(long initialDelay, TimeUnit timeUnit); /** * Sets execution period. + * * @param period time between executions * @param timeUnit time unit for period + * @return this builder for method chaining */ TaskBuilder withPeriod(long period, TimeUnit timeUnit); /** * Uses fixed delay scheduling. * Period is measured from completion of one execution to start of next. + * + * @return this builder for method chaining */ TaskBuilder withFixedDelay(); /** * Uses fixed rate scheduling. * Period is measured from start of one execution to start of next. + * + * @return this builder for method chaining */ TaskBuilder withFixedRate(); /** * Makes this a one-shot task. * Task will execute once and then be disabled. + * + * @return this builder for method chaining */ TaskBuilder asOneShot(); /** * Disallows parallel execution. * Task will use locking to ensure only one instance runs at a time. + * + * @return this builder for method chaining */ TaskBuilder disallowParallelExecution(); /** * Sets the task executor. + * * @param executor the executor to handle this task + * @return this builder for method chaining */ TaskBuilder withExecutor(TaskExecutor executor); /** * Sets a simple runnable as the executor. + * * @param runnable the code to execute + * @return this builder for method chaining */ TaskBuilder withSimpleExecutor(Runnable runnable); /** * Makes this a non-persistent task. * Task will only exist in memory on this node. + * + * @return this builder for method chaining */ TaskBuilder nonPersistent(); /** * Runs the task on all nodes in the cluster rather than just executor nodes. * This is helpful for distributed cache refreshes or local data maintenance. + * + * @return this builder for method chaining */ TaskBuilder runOnAllNodes(); @@ -381,6 +413,7 @@ interface TaskBuilder { * * @param maxRetries maximum number of retries (must be >= 0) * @throws IllegalArgumentException if maxRetries is negative + * @return this builder for method chaining */ TaskBuilder withMaxRetries(int maxRetries); @@ -397,18 +430,22 @@ interface TaskBuilder { * @param delay delay duration (must be >= 0) * @param unit time unit for delay * @throws IllegalArgumentException if delay is negative + * @return this builder for method chaining */ TaskBuilder withRetryDelay(long delay, TimeUnit unit); /** * Sets the task dependencies. * The task will not execute until all dependencies have completed. + * * @param taskIds IDs of tasks this task depends on + * @return this builder for method chaining */ TaskBuilder withDependencies(String... taskIds); /** * Creates and schedules the task with current configuration. + * * @return the created and scheduled task */ ScheduledTask schedule(); diff --git a/api/src/main/java/org/apache/unomi/api/services/ScopeService.java b/api/src/main/java/org/apache/unomi/api/services/ScopeService.java index 91980a407..d9c223697 100644 --- a/api/src/main/java/org/apache/unomi/api/services/ScopeService.java +++ b/api/src/main/java/org/apache/unomi/api/services/ScopeService.java @@ -22,7 +22,9 @@ import java.util.List; /** - * A service to create, update and delete scope. + * Manages {@link Scope} definitions. + * Creates, updates, and deletes scopes that partition data between sites + * or applications. */ public interface ScopeService { @@ -51,6 +53,7 @@ public interface ScopeService { /** * Get a scope by its id * + * @param id scope identifier * @return Scope matching the id */ Scope getScope(String id); diff --git a/api/src/main/java/org/apache/unomi/api/services/SegmentService.java b/api/src/main/java/org/apache/unomi/api/services/SegmentService.java index 601cd5633..26eeb11a4 100644 --- a/api/src/main/java/org/apache/unomi/api/services/SegmentService.java +++ b/api/src/main/java/org/apache/unomi/api/services/SegmentService.java @@ -31,51 +31,46 @@ import java.util.List; /** - * A service to access and operate on {@link Segment}s and {@link Scoring}s + * Manages {@link org.apache.unomi.api.segments.Segment}s and + * {@link org.apache.unomi.api.segments.Scoring} definitions. + * Also recalculates segment membership for profiles when definitions change. */ public interface SegmentService { /** - * Retrieves segment metadatas, ordered according to the specified {@code sortBy} String and and paged: only {@code size} of them are retrieved, starting with the {@code - * offset}-th one. + * Returns segment metadata, ordered and paged. * - * @param offset zero or a positive integer specifying the position of the first element in the total ordered collection of matching elements - * @param size a positive integer specifying how many matching elements should be retrieved or {@code -1} if all of them should be retrieved - * @param sortBy an optional ({@code null} if no sorting is required) String of comma ({@code ,}) separated property names on which ordering should be performed, ordering elements according to the property order in the - * String, considering each in turn and moving on to the next one in case of equality of all preceding ones. Each property name is optionally followed by - * a column ({@code :}) and an order specifier: {@code asc} or {@code desc}. - * @return a {@link PartialList} of segment metadata + * @param offset zero-based index of the first result + * @param size maximum results to return, or {@code -1} for all + * @param sortBy optional comma-separated property list with optional {@code :asc}/{@code :desc} suffixes + * @return segment metadata */ PartialList getSegmentMetadatas(int offset, int size, String sortBy); /** - * Retrieves segment metadatas for segments in the specified scope, ordered according to the specified {@code sortBy} String and and paged: only {@code size} of them are - * retrieved, starting with the {@code offset}-th one. - * TODO: remove? + * Returns segment metadata for a scope, ordered and paged. * - * @param scope the scope for which we want to retrieve segment metadata - * @param offset zero or a positive integer specifying the position of the first element in the total ordered collection of matching elements - * @param size a positive integer specifying how many matching elements should be retrieved or {@code -1} if all of them should be retrieved - * @param sortBy an optional ({@code null} if no sorting is required) String of comma ({@code ,}) separated property names on which ordering should be performed, ordering elements according to the property order in the - * String, considering each in turn and moving on to the next one in case of equality of all preceding ones. Each property name is optionally followed by - * a column ({@code :}) and an order specifier: {@code asc} or {@code desc}. - * @return a {@link PartialList} of segment metadata + * @param scope scope filter + * @param offset zero-based index of the first result + * @param size maximum results to return, or {@code -1} for all + * @param sortBy optional comma-separated property list with optional {@code :asc}/{@code :desc} suffixes + * @return segment metadata for the scope */ PartialList getSegmentMetadatas(String scope, int offset, int size, String sortBy); /** - * Retrieves the metadata for segments matching the specified {@link Query}. + * Returns segment metadata matching the given query. * - * @param query the query that the segments must match for their metadata to be retrieved - * @return a {@link PartialList} of segment metadata + * @param query filter for segments whose metadata should be returned + * @return matching segment metadata */ PartialList getSegmentMetadatas(Query query); /** - * Retrieves the segment identified by the specified identifier. + * Loads a segment definition by id. * - * @param segmentId the identifier of the segment to be retrieved - * @return the segment identified by the specified identifier or {@code null} if no such segment exists + * @param segmentId segment identifier + * @return matching segment, or {@code null} if none exists */ Segment getSegmentDefinition(String segmentId); @@ -99,33 +94,30 @@ public interface SegmentService { DependentMetadata removeSegmentDefinition(String segmentId, boolean validate); /** - * Retrieves the list of Segment and Scoring metadata depending on the specified segment. - * A segment or scoring is depending on a segment if it includes a profileSegmentCondition with a test on this segment. + * Returns segment and scoring metadata that depend on the given segment. + * A dependent definition references the segment in a profile-segment condition. * - * @param segmentId the segment identifier - * @return a list of Segment/Scoring Metadata depending on the specified segment + * @param segmentId segment identifier + * @return dependent segment and scoring metadata */ DependentMetadata getSegmentDependentMetadata(String segmentId); /** - * Retrieves a list of profiles matching the conditions defined by the segment identified by the specified identifier, ordered according to the specified {@code sortBy} - * String and and paged: only {@code size} of them are retrieved, starting with the {@code offset}-th one. + * Lists profiles that match a segment's conditions, ordered and paged. * - * @param segmentID the identifier of the segment for which we want to retrieve matching profiles - * @param offset zero or a positive integer specifying the position of the first element in the total ordered collection of matching elements - * @param size a positive integer specifying how many matching elements should be retrieved or {@code -1} if all of them should be retrieved - * @param sortBy an optional ({@code null} if no sorting is required) String of comma ({@code ,}) separated property names on which ordering should be performed, ordering elements according to the property order in the - * String, considering each in turn and moving on to the next one in case of equality of all preceding ones. Each property name is optionally followed by - * a column ({@code :}) and an order specifier: {@code asc} or {@code desc}. - * @return a {@link PartialList} of profiles matching the specified segment + * @param segmentID segment identifier + * @param offset zero-based index of the first result + * @param size maximum results to return, or {@code -1} for all + * @param sortBy optional comma-separated property list with optional {@code :asc}/{@code :desc} suffixes + * @return matching profiles */ PartialList getMatchingIndividuals(String segmentID, int offset, int size, String sortBy); /** - * Retrieves the number of profiles matching the conditions defined by the segment identified by the specified identifier. + * Counts profiles that match a segment's conditions. * - * @param segmentID the identifier of the segment for which we want to retrieve matching profiles - * @return the number of profiles matching the conditions defined by the segment identified by the specified identifier + * @param segmentID segment identifier + * @return matching profile count */ long getMatchingIndividualsCount(String segmentID); @@ -139,44 +131,44 @@ public interface SegmentService { Boolean isProfileInSegment(Profile profile, String segmentId); /** - * Retrieves the segments and scores for the specified profile. + * Returns current segment memberships and scores for a profile. * - * @param profile the profile for which we want to retrieve segments and scores - * @return a {@link SegmentsAndScores} instance encapsulating the segments and scores for the specified profile + * @param profile profile to evaluate + * @return segments and scores for the profile */ SegmentsAndScores getSegmentsAndScoresForProfile(Profile profile); /** - * Retrieves the list of segment metadata for the segments the specified profile is a member of. + * Returns metadata for segments the profile belongs to. * - * @param profile the profile for which we want to retrieve the segment metadata - * @return the (possibly empty) list of segment metadata for the segments the specified profile is a member of + * @param profile profile to inspect + * @return segment metadata for the profile's memberships */ List getSegmentMetadatasForProfile(Profile profile); /** - * Retrieves the set of all scoring metadata. + * Returns scoring metadata, ordered and paged. * - * @param offset the offset - * @param size the size - * @param sortBy sort by - * @return the set of all scoring metadata + * @param offset zero-based index of the first result + * @param size maximum results to return, or {@code -1} for all + * @param sortBy optional comma-separated property list with optional {@code :asc}/{@code :desc} suffixes + * @return scoring metadata */ PartialList getScoringMetadatas(int offset, int size, String sortBy); /** - * Retrieves the set of scoring metadata for scorings matching the specified query. + * Returns scoring metadata matching the given query. * - * @param query the query the scorings must match for their metadata to be retrieved - * @return the set of scoring metadata for scorings matching the specified query + * @param query filter for scorings whose metadata should be returned + * @return matching scoring metadata */ PartialList getScoringMetadatas(Query query); /** - * Retrieves the scoring identified by the specified identifier. + * Loads a scoring definition by id. * - * @param scoringId the identifier of the scoring to be retrieved - * @return the scoring identified by the specified identifier or {@code null} if no such scoring exists + * @param scoringId scoring identifier + * @return matching scoring, or {@code null} if none exists */ Scoring getScoringDefinition(String scoringId); @@ -211,19 +203,20 @@ public interface SegmentService { DependentMetadata removeScoringDefinition(String scoringId, boolean validate); /** - * Retrieves the list of Segment and Scoring metadata depending on the specified scoring. - * A segment or scoring is depending on a segment if it includes a scoringCondition with a test on this scoring. + * Returns segment and scoring metadata that depend on the given scoring. + * A dependent definition references the scoring in a scoring condition. * - * @param scoringId the segment identifier - * @return a list of Segment/Scoring Metadata depending on the specified scoring + * @param scoringId scoring identifier + * @return dependent segment and scoring metadata */ DependentMetadata getScoringDependentMetadata(String scoringId); /** - * Get generated property key for past event condition - * @param condition The event condition - * @param parentCondition The past event condition - * @return a String representing the condition and parent condition uniquelly + * Builds a stable property key for a past-event condition pair. + * + * @param condition nested event condition + * @param parentCondition parent past-event condition + * @return generated property key */ String getGeneratedPropertyKey(Condition condition, Condition parentCondition); diff --git a/api/src/main/java/org/apache/unomi/api/services/TenantLifecycleListener.java b/api/src/main/java/org/apache/unomi/api/services/TenantLifecycleListener.java index 5aab7ad78..1d1b9ccf7 100644 --- a/api/src/main/java/org/apache/unomi/api/services/TenantLifecycleListener.java +++ b/api/src/main/java/org/apache/unomi/api/services/TenantLifecycleListener.java @@ -17,12 +17,14 @@ package org.apache.unomi.api.services; /** - * Interface for services that need to be notified of tenant lifecycle events. + * Extension point notified when tenants are created, updated, or removed. + * Implementations run setup or teardown logic for multi-tenant deployments. */ public interface TenantLifecycleListener { /** * Called when a tenant is removed from the system. + * * @param tenantId the ID of the tenant that was removed */ void onTenantRemoved(String tenantId); -} \ No newline at end of file +} \ No newline at end of file diff --git a/api/src/main/java/org/apache/unomi/api/services/TopicService.java b/api/src/main/java/org/apache/unomi/api/services/TopicService.java index 6cb5e0b38..6a843c5db 100644 --- a/api/src/main/java/org/apache/unomi/api/services/TopicService.java +++ b/api/src/main/java/org/apache/unomi/api/services/TopicService.java @@ -20,37 +20,42 @@ import org.apache.unomi.api.Topic; import org.apache.unomi.api.query.Query; +/** + * Persistence API for {@link Topic} items. + * Supports load, save, search, and delete of topics used to categorize + * entities across the context server. + */ public interface TopicService { /** - * Retrieves the topic identified by the specified identifier. + * Loads a topic by id. * - * @param topicId the identifier of the topic to retrieve - * @return the topic identified by the specified identifier or {@code null} if no such topic exists + * @param topicId topic identifier + * @return matching topic, or {@code null} if none exists */ Topic load(final String topicId); /** - * Saves the specified topic in the context server. + * Persists a topic. * - * @param topic the topic to be saved - * @return the newly saved topic if the creation or update was successful, {@code null} otherwise + * @param topic topic to save + * @return saved topic, or {@code null} if the operation failed */ Topic save(final Topic topic); /** - * Retrieves topic matching the specified query. + * Searches topics using a structured query. * - * @param query a {@link Query} specifying which elements to retrieve - * @return a {@link PartialList} of {@link Topic} metadata + * @param query search query + * @return matching topics */ PartialList search(final Query query); /** - * Removes the topic identified by the specified identifier. + * Deletes a topic by id. * - * @param topicId the identifier of the profile or persona to delete - * @return {@code true} if the deletion was successful, {@code false} otherwise + * @param topicId topic identifier + * @return {@code true} if deletion succeeded */ boolean delete(final String topicId); diff --git a/api/src/main/java/org/apache/unomi/api/services/UserListService.java b/api/src/main/java/org/apache/unomi/api/services/UserListService.java index 04d109ab7..7612bde0a 100644 --- a/api/src/main/java/org/apache/unomi/api/services/UserListService.java +++ b/api/src/main/java/org/apache/unomi/api/services/UserListService.java @@ -24,7 +24,8 @@ import java.util.List; /** - * Created by amidani on 24/03/2017. + * CRUD and membership operations for {@link UserList} instances. + * Maintains static audience lists referenced by campaigns and exports. */ public interface UserListService { /** diff --git a/api/src/main/java/org/apache/unomi/api/services/ValueTypeValidator.java b/api/src/main/java/org/apache/unomi/api/services/ValueTypeValidator.java index ec6a3e255..b330286b5 100644 --- a/api/src/main/java/org/apache/unomi/api/services/ValueTypeValidator.java +++ b/api/src/main/java/org/apache/unomi/api/services/ValueTypeValidator.java @@ -17,7 +17,8 @@ package org.apache.unomi.api.services; /** - * A service interface for validating values against specific types. + * Validates a value against a {@link org.apache.unomi.api.ValueType} definition. + * Called when saving properties or condition parameters to enforce schema rules. */ public interface ValueTypeValidator { @@ -28,6 +29,7 @@ public interface ValueTypeValidator { /** * Validates if a value matches the expected type + * * @param value The value to validate * @return true if the value is valid for this type, false otherwise */ 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 e8afce678..c5d698e77 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 @@ -105,7 +105,7 @@ public enum TaskStatus { private Set waitingOnTasks = new HashSet<>(); /** - * Instantiates a new scheduled task with default status {@link TaskStatus#SCHEDULED}, + * Creates a scheduled task with default status {@link TaskStatus#SCHEDULED}, * {@code maxRetries} of 3, and a default {@code retryDelay} of 60 seconds. */ public ScheduledTask() { @@ -117,7 +117,7 @@ public ScheduledTask() { } /** - * Retrieves the task type identifier. + * Task type identifier that selects the executor. * The task type determines which executor will handle this task. * * @return the task type identifier @@ -136,7 +136,7 @@ public void setTaskType(String taskType) { } /** - * Retrieves the task parameters. + * Parameters passed to the task executor. * These parameters are passed to the task executor during execution. * * @return map of task parameters @@ -155,7 +155,7 @@ public void setParameters(Map parameters) { } /** - * Retrieves the initial delay before the first execution, expressed in {@link #getTimeUnit()}. + * Initial delay before the first execution, expressed in {@link #getTimeUnit()}. * * @return the initial delay in the configured time unit */ @@ -173,7 +173,7 @@ public void setInitialDelay(long initialDelay) { } /** - * Retrieves the period between successive task executions. + * 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 @@ -204,7 +204,7 @@ public void setPeriod(long period) { } /** - * Retrieves the time unit for delay and period values. + * Time unit for delay and period values. * * @return the time unit used for scheduling */ @@ -242,7 +242,7 @@ public void setFixedRate(boolean fixedRate) { } /** - * Retrieves the date of the last execution attempt. + * Date of the last execution attempt. * * @return the last execution date or {@code null} if never executed */ @@ -260,7 +260,7 @@ public void setLastExecutionDate(Date lastExecutionDate) { } /** - * Retrieves the node ID that last executed this task. + * Node identifier that last executed this task. * * @return the ID of the last executing node */ @@ -278,7 +278,7 @@ public void setLastExecutedBy(String lastExecutedBy) { } /** - * Retrieves the error message from the last failed execution. + * Error message from the last failed execution. * * @return the last error message or {@code null} if no error */ @@ -315,7 +315,7 @@ public void setEnabled(boolean enabled) { } /** - * Retrieves the ID of the node that currently holds the execution lock. + * Node identifier that currently holds the execution lock. * * @return the current lock owner's node ID or {@code null} if unlocked */ @@ -333,7 +333,7 @@ public void setLockOwner(String lockOwner) { } /** - * Retrieves the date when the current lock was acquired. + * Date when the current lock was acquired. * * @return the lock acquisition date or {@code null} if unlocked */ @@ -395,7 +395,7 @@ public void setAllowParallelExecution(boolean allowParallelExecution) { } /** - * Retrieves the current task status. + * Current task lifecycle status. * * @return the current status */ @@ -433,7 +433,7 @@ public void setStatusDetails(Map statusDetails) { } /** - * Retrieves the next scheduled execution date for periodic tasks. + * Next scheduled execution date for periodic tasks. * * @return the next scheduled execution date or {@code null} if not scheduled */ @@ -451,7 +451,7 @@ public void setNextScheduledExecution(Date nextScheduledExecution) { } /** - * Retrieves the number of consecutive execution failures. + * Number of consecutive execution failures. * * @return the failure count */ @@ -469,7 +469,7 @@ public void setFailureCount(int failureCount) { } /** - * Retrieves the number of successful executions. + * Number of successful executions. * * @return the success count */ @@ -487,7 +487,7 @@ public void setSuccessCount(int successCount) { } /** - * Retrieves the maximum number of retry attempts after failures. + * 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 @@ -516,7 +516,7 @@ public void setMaxRetries(int maxRetries) { } /** - * Retrieves the delay between retry attempts. + * 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 @@ -541,7 +541,7 @@ public void setRetryDelay(long retryDelay) { } /** - * Retrieves the name of the current execution step. + * Name of the current execution step. * This is used to track progress through multi-step tasks. * * @return the current step name or {@code null} if not set @@ -560,7 +560,7 @@ public void setCurrentStep(String currentStep) { } /** - * Retrieves the checkpoint data for task resumption. + * 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 {@code null} if no checkpoint @@ -642,7 +642,7 @@ public void setSystemTask(boolean systemTask) { } /** - * Retrieves the task type that this task is waiting for a lock on. + * Task type 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 {@code null} if not waiting @@ -661,7 +661,7 @@ public void setWaitingForTaskType(String waitingForTaskType) { } /** - * Retrieves the set of task IDs that this task depends on. + * Task identifiers this task depends on. * The task will not execute until all dependencies have completed. * * @return set of dependency task IDs @@ -680,7 +680,7 @@ public void setDependsOn(Set dependsOn) { } /** - * Retrieves the set of task IDs that this task is currently waiting on. + * Task identifiers 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 @@ -746,7 +746,7 @@ public void removeWaitingOnTask(String taskId) { } /** - * Retrieves the ID of the node currently executing this task. + * Node identifier 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. * diff --git a/api/src/main/java/org/apache/unomi/api/tenants/ApiKey.java b/api/src/main/java/org/apache/unomi/api/tenants/ApiKey.java index 93f57f2a3..b6121792f 100644 --- a/api/src/main/java/org/apache/unomi/api/tenants/ApiKey.java +++ b/api/src/main/java/org/apache/unomi/api/tenants/ApiKey.java @@ -22,9 +22,9 @@ import java.util.Date; /** - * Represents an API key for tenant authentication and authorization. - * This class extends the base Item class and provides functionality for managing - * API keys including their lifecycle (creation, expiration, revocation) and metadata. + * Persisted credential used to authenticate REST calls for a {@link Tenant}. + * Stores hashed key material, scope, expiration, and revocation state. Plaintext + * keys are only returned once at creation time via {@link ApiKeyCreationResult}. */ public class ApiKey extends Item { /** @@ -142,6 +142,7 @@ public static String maskPlainTextKey(String plainTextKey) { /** * Gets the SHA-256 digest of the API key. + * * @return the key hash as lowercase hex */ public String getKeyHash() { @@ -150,6 +151,7 @@ public String getKeyHash() { /** * Sets the SHA-256 digest of the API key. + * * @param keyHash the key hash to set */ public void setKeyHash(String keyHash) { @@ -158,6 +160,7 @@ public void setKeyHash(String keyHash) { /** * Gets the display-safe masked representation of the key. + * * @return the masked key (e.g. "unomi_v1_****ab12") */ public String getMaskedKey() { @@ -166,6 +169,7 @@ public String getMaskedKey() { /** * Sets the display-safe masked representation of the key. + * * @param maskedKey the masked key to set */ public void setMaskedKey(String maskedKey) { @@ -174,6 +178,7 @@ public void setMaskedKey(String maskedKey) { /** * Gets the name or identifier of the API key. + * * @return the API key name */ public String getName() { @@ -182,6 +187,7 @@ public String getName() { /** * Sets the name or identifier of the API key. + * * @param name the API key name to set */ public void setName(String name) { @@ -190,6 +196,7 @@ public void setName(String name) { /** * Gets the description of the API key's purpose or usage. + * * @return the API key description */ public String getDescription() { @@ -198,6 +205,7 @@ public String getDescription() { /** * Sets the description of the API key's purpose or usage. + * * @param description the API key description to set */ public void setDescription(String description) { @@ -206,6 +214,7 @@ public void setDescription(String description) { /** * Gets the creation date of the API key. + * * @return the creation date */ @Override @@ -215,6 +224,7 @@ public Date getCreationDate() { /** * Sets the creation date of the API key. + * * @param creationDate the creation date to set */ public void setCreationDate(Date creationDate) { @@ -223,6 +233,7 @@ public void setCreationDate(Date creationDate) { /** * Gets the expiration date of the API key. + * * @return the expiration date */ public Date getExpirationDate() { @@ -231,6 +242,7 @@ public Date getExpirationDate() { /** * Sets the expiration date of the API key. + * * @param expirationDate the expiration date to set */ public void setExpirationDate(Date expirationDate) { @@ -239,6 +251,7 @@ public void setExpirationDate(Date expirationDate) { /** * Checks if the API key has been revoked. + * * @return true if the API key is revoked, false otherwise */ public boolean isRevoked() { @@ -247,6 +260,7 @@ public boolean isRevoked() { /** * Sets the revocation status of the API key. + * * @param revoked true to revoke the API key, false to reinstate */ public void setRevoked(boolean revoked) { @@ -255,6 +269,7 @@ public void setRevoked(boolean revoked) { /** * Gets the type of the API key. + * * @return the API key type */ public ApiKeyType getKeyType() { @@ -263,6 +278,7 @@ public ApiKeyType getKeyType() { /** * Sets the type of the API key. + * * @param keyType the API key type to set */ public void setKeyType(ApiKeyType keyType) { diff --git a/api/src/main/java/org/apache/unomi/api/tenants/ApiKeyConfig.java b/api/src/main/java/org/apache/unomi/api/tenants/ApiKeyConfig.java index aada3c21b..14e94cd5a 100644 --- a/api/src/main/java/org/apache/unomi/api/tenants/ApiKeyConfig.java +++ b/api/src/main/java/org/apache/unomi/api/tenants/ApiKeyConfig.java @@ -20,9 +20,9 @@ import java.util.Map; /** - * Configuration settings for API keys. - * This class defines the configuration parameters for API key management, - * including validation rules and usage limits. + * Policy constraints applied when creating or validating {@link ApiKey} instances. + * Defines length and pattern rules, expiration defaults, allowed scopes, active-key + * limits, and optional per-scope rate limits consumed by tenant security services. */ public class ApiKeyConfig { private int minLength; @@ -36,6 +36,7 @@ public class ApiKeyConfig { /** * Gets the minimum length required for API keys. + * * @return the minimum length */ public int getMinLength() { @@ -44,6 +45,7 @@ public int getMinLength() { /** * Sets the minimum length required for API keys. + * * @param minLength the minimum length to set */ public void setMinLength(int minLength) { @@ -52,6 +54,7 @@ public void setMinLength(int minLength) { /** * Gets the maximum length allowed for API keys. + * * @return the maximum length */ public int getMaxLength() { @@ -60,6 +63,7 @@ public int getMaxLength() { /** * Sets the maximum length allowed for API keys. + * * @param maxLength the maximum length to set */ public void setMaxLength(int maxLength) { @@ -68,6 +72,7 @@ public void setMaxLength(int maxLength) { /** * Gets the regex pattern for API key validation. + * * @return the validation pattern */ public String getPattern() { @@ -76,6 +81,7 @@ public String getPattern() { /** * Sets the regex pattern for API key validation. + * * @param pattern the validation pattern to set */ public void setPattern(String pattern) { @@ -84,6 +90,7 @@ public void setPattern(String pattern) { /** * Gets the maximum number of active API keys allowed. + * * @return the maximum number of active keys */ public int getMaxActiveKeys() { @@ -92,6 +99,7 @@ public int getMaxActiveKeys() { /** * Sets the maximum number of active API keys allowed. + * * @param maxActiveKeys the maximum number to set */ public void setMaxActiveKeys(int maxActiveKeys) { @@ -100,6 +108,7 @@ public void setMaxActiveKeys(int maxActiveKeys) { /** * Gets the default expiration period in days for new API keys. + * * @return the default expiration period in days */ public int getDefaultExpirationDays() { @@ -108,6 +117,7 @@ public int getDefaultExpirationDays() { /** * Sets the default expiration period in days for new API keys. + * * @param defaultExpirationDays the default expiration period to set */ public void setDefaultExpirationDays(int defaultExpirationDays) { @@ -116,6 +126,7 @@ public void setDefaultExpirationDays(int defaultExpirationDays) { /** * Gets the list of allowed scopes for API keys. + * * @return list of allowed scopes */ public List getAllowedScopes() { @@ -124,6 +135,7 @@ public List getAllowedScopes() { /** * Sets the list of allowed scopes for API keys. + * * @param allowedScopes list of allowed scopes to set */ public void setAllowedScopes(List allowedScopes) { @@ -132,6 +144,7 @@ public void setAllowedScopes(List allowedScopes) { /** * Gets the rate limits for different operations. + * * @return map of operation names to their rate limits */ public Map getRateLimits() { @@ -140,6 +153,7 @@ public Map getRateLimits() { /** * Sets the rate limits for different operations. + * * @param rateLimits map of operation names to their rate limits */ public void setRateLimits(Map rateLimits) { @@ -148,6 +162,7 @@ public void setRateLimits(Map rateLimits) { /** * Gets additional configuration settings. + * * @return map of additional settings */ public Map getAdditionalSettings() { @@ -156,6 +171,7 @@ public Map getAdditionalSettings() { /** * Sets additional configuration settings. + * * @param additionalSettings map of additional settings to set */ public void setAdditionalSettings(Map additionalSettings) { diff --git a/api/src/main/java/org/apache/unomi/api/tenants/ApiKeyCreationResult.java b/api/src/main/java/org/apache/unomi/api/tenants/ApiKeyCreationResult.java index 69d48e0af..8bbea6daa 100644 --- a/api/src/main/java/org/apache/unomi/api/tenants/ApiKeyCreationResult.java +++ b/api/src/main/java/org/apache/unomi/api/tenants/ApiKeyCreationResult.java @@ -28,9 +28,23 @@ public class ApiKeyCreationResult { private ApiKey apiKey; private String plainTextKey; + /** + * Constructs an empty {@link ApiKeyCreationResult}. + * This result object must be populated manually with the created API key + * metadata and the plaintext key value. + */ public ApiKeyCreationResult() { } + /** + * Creates a result containing both the persisted {@link ApiKey} metadata + * and the plaintext key value. + * The plaintext key is only available at creation time, as + * it is not persisted. + * + * @param apiKey the API key metadata that was successfully persisted. + * @param plainTextKey the one-time plaintext key value. + */ public ApiKeyCreationResult(ApiKey apiKey, String plainTextKey) { this.apiKey = apiKey; this.plainTextKey = plainTextKey; @@ -38,6 +52,7 @@ public ApiKeyCreationResult(ApiKey apiKey, String plainTextKey) { /** * Gets the persisted API key metadata (type, masked key, dates, etc.), without the secret. + * * @return the API key metadata */ public ApiKey getApiKey() { @@ -46,6 +61,7 @@ public ApiKey getApiKey() { /** * Sets the persisted API key metadata. + * * @param apiKey the API key metadata to set */ public void setApiKey(ApiKey apiKey) { @@ -55,6 +71,7 @@ public void setApiKey(ApiKey apiKey) { /** * Gets the one-time plaintext key value. This is only available right after creation; * it is never persisted and cannot be retrieved again afterwards. + * * @return the plaintext API key */ public String getPlainTextKey() { @@ -63,6 +80,7 @@ public String getPlainTextKey() { /** * Sets the one-time plaintext key value. + * * @param plainTextKey the plaintext API key to set */ public void setPlainTextKey(String plainTextKey) { diff --git a/api/src/main/java/org/apache/unomi/api/tenants/AuditService.java b/api/src/main/java/org/apache/unomi/api/tenants/AuditService.java index 6fb6460af..1c9fde333 100644 --- a/api/src/main/java/org/apache/unomi/api/tenants/AuditService.java +++ b/api/src/main/java/org/apache/unomi/api/tenants/AuditService.java @@ -22,7 +22,8 @@ import java.util.List; /** - * Combined service interface for both item and tenant auditing operations. + * Combined facade for item-level and tenant-level audit trails. + * Delegates to {@link ItemAuditService} and {@link TenantAuditService}. */ public interface AuditService extends ItemAuditService, TenantAuditService { /** @@ -50,20 +51,20 @@ public interface AuditService extends ItemAuditService, TenantAuditService { void auditDelete(Item item, String userId); /** - * Retrieves items modified since a specific date. + * Returns configuration items modified after the given date. * - * @param tenantId the tenant ID to filter by - * @param since the date to check modifications from - * @return a list of modified items + * @param tenantId tenant identifier + * @param since earliest modification timestamp (exclusive) + * @return modified items */ List getModifiedItems(String tenantId, Date since); /** - * Retrieves items modified since the last synchronization. + * Returns items modified since the last sync with a source instance. * - * @param tenantId the tenant ID to filter by - * @param sourceInstanceId the source instance ID - * @return a list of modified items + * @param tenantId tenant identifier + * @param sourceInstanceId source cluster node identifier + * @return modified items since last sync */ List getModifiedItemsSinceLastSync(String tenantId, String sourceInstanceId); @@ -77,14 +78,20 @@ public interface AuditService extends ItemAuditService, TenantAuditService { void updateLastSyncDate(String tenantId, String sourceInstanceId, Date syncDate); /** - * Retrieves the last synchronization date. + * Returns the last sync timestamp for a tenant and source instance pair. * - * @param tenantId the tenant ID - * @param sourceInstanceId the source instance ID - * @return the last synchronization date + * @param tenantId tenant identifier + * @param sourceInstanceId source cluster node identifier + * @return last synchronization date */ Date getLastSyncDate(String tenantId, String sourceInstanceId); + /** + * Updates last-modified audit fields on an item. + * + * @param item the item to update + * @param userId the user performing the modification + */ default void updateModificationMetadata(Item item, String userId) { item.setLastModifiedBy(userId); item.setLastModificationDate(new Date()); diff --git a/api/src/main/java/org/apache/unomi/api/tenants/ItemAuditService.java b/api/src/main/java/org/apache/unomi/api/tenants/ItemAuditService.java index 77b5bb2c2..ff168c4cf 100644 --- a/api/src/main/java/org/apache/unomi/api/tenants/ItemAuditService.java +++ b/api/src/main/java/org/apache/unomi/api/tenants/ItemAuditService.java @@ -22,7 +22,8 @@ import java.util.List; /** - * A service to track and audit changes to items. + * Records create, update, and delete operations on configuration items. + * Provides an audit trail for who changed segments, rules, and similar entities. */ public interface ItemAuditService { /** @@ -50,20 +51,20 @@ public interface ItemAuditService { void auditDelete(Item item, String userId); /** - * Retrieves items modified since a specific date. + * Returns configuration items modified after the given date. * - * @param tenantId the tenant ID to filter by - * @param since the date to check modifications from - * @return a list of modified items + * @param tenantId tenant identifier + * @param since earliest modification timestamp (exclusive) + * @return modified items */ List getModifiedItems(String tenantId, Date since); /** - * Retrieves items modified since the last synchronization. + * Returns items modified since the last sync with a source instance. * - * @param tenantId the tenant ID to filter by - * @param sourceInstanceId the source instance ID - * @return a list of modified items + * @param tenantId tenant identifier + * @param sourceInstanceId source cluster node identifier + * @return modified items since last sync */ List getModifiedItemsSinceLastSync(String tenantId, String sourceInstanceId); @@ -77,11 +78,11 @@ public interface ItemAuditService { void updateLastSyncDate(String tenantId, String sourceInstanceId, Date syncDate); /** - * Retrieves the last synchronization date. + * Returns the last sync timestamp for a tenant and source instance pair. * - * @param tenantId the tenant ID - * @param sourceInstanceId the source instance ID - * @return the last synchronization date + * @param tenantId tenant identifier + * @param sourceInstanceId source cluster node identifier + * @return last synchronization date */ Date getLastSyncDate(String tenantId, String sourceInstanceId); @@ -95,4 +96,4 @@ default void updateModificationMetadata(Item item, String userId) { item.setLastModifiedBy(userId); item.setLastModificationDate(new Date()); } -} \ No newline at end of file +} \ No newline at end of file diff --git a/api/src/main/java/org/apache/unomi/api/tenants/Tenant.java b/api/src/main/java/org/apache/unomi/api/tenants/Tenant.java index 6de2f11fe..c9c8abad9 100644 --- a/api/src/main/java/org/apache/unomi/api/tenants/Tenant.java +++ b/api/src/main/java/org/apache/unomi/api/tenants/Tenant.java @@ -25,12 +25,8 @@ import javax.xml.bind.annotation.XmlTransient; /** - * Represents a tenant in the system. - * A tenant is an isolated entity within the system with its own users, data, and configuration. - * Each tenant has its own set of API keys (public and private) for authentication and authorization, - * and event permissions to control access to specific event types. - * This class extends the base Item class and provides functionality for managing tenant - * settings and lifecycle. + * A tenant isolates data, API keys, and event permissions within a single Unomi deployment. + * Each tenant has its own users, configuration, and lifecycle settings. */ public class Tenant extends Item { /** @@ -101,6 +97,7 @@ public Tenant() { /** * Gets the tenant's display name. + * * @return the tenant name */ public String getName() { @@ -109,6 +106,7 @@ public String getName() { /** * Sets the tenant's display name. + * * @param name the tenant name to set */ public void setName(String name) { @@ -117,6 +115,7 @@ public void setName(String name) { /** * Gets the tenant's description. + * * @return the tenant description */ public String getDescription() { @@ -125,6 +124,7 @@ public String getDescription() { /** * Sets the tenant's description. + * * @param description the tenant description to set */ public void setDescription(String description) { @@ -133,6 +133,7 @@ public void setDescription(String description) { /** * Gets the tenant's current status. + * * @return the tenant status */ public TenantStatus getStatus() { @@ -141,6 +142,7 @@ public TenantStatus getStatus() { /** * Sets the tenant's status. + * * @param status the tenant status to set */ public void setStatus(TenantStatus status) { @@ -149,6 +151,7 @@ public void setStatus(TenantStatus status) { /** * Gets the tenant's creation date. + * * @return the creation date */ @Override @@ -158,6 +161,7 @@ public Date getCreationDate() { /** * Sets the tenant's creation date. + * * @param creationDate the creation date to set */ public void setCreationDate(Date creationDate) { @@ -166,6 +170,7 @@ public void setCreationDate(Date creationDate) { /** * Gets the tenant's last modification date. + * * @return the last modification date */ @Override @@ -175,6 +180,7 @@ public Date getLastModificationDate() { /** * Sets the tenant's last modification date. + * * @param lastModificationDate the last modification date to set */ public void setLastModificationDate(Date lastModificationDate) { @@ -184,6 +190,7 @@ public void setLastModificationDate(Date lastModificationDate) { /** * Gets the list of all API keys associated with the tenant. * This includes both active and historical keys for auditing purposes. + * * @return the list of API keys */ public List getApiKeys() { @@ -192,6 +199,7 @@ public List getApiKeys() { /** * Sets the list of API keys associated with the tenant. + * * @param apiKeys the list of API keys to set */ public void setApiKeys(List apiKeys) { @@ -200,6 +208,7 @@ public void setApiKeys(List apiKeys) { /** * Gets additional tenant properties as key-value pairs. + * * @return map of additional properties */ public Map getProperties() { @@ -208,6 +217,7 @@ public Map getProperties() { /** * Sets additional tenant properties as key-value pairs. + * * @param properties map of additional properties to set */ public void setProperties(Map properties) { @@ -217,6 +227,7 @@ public void setProperties(Map properties) { /** * Gets the set of event types that are restricted for this tenant. * Events of these types will require IP validation before being processed. + * * @return the set of restricted event types */ public Set getRestrictedEventTypes() { @@ -226,6 +237,7 @@ public Set getRestrictedEventTypes() { /** * Sets the event types that are restricted for this tenant. * Events of these types will require IP validation before being processed. + * * @param restrictedEventTypes the set of restricted event types to set */ public void setRestrictedEventTypes(Set restrictedEventTypes) { @@ -234,6 +246,7 @@ public void setRestrictedEventTypes(Set restrictedEventTypes) { /** * Gets the set of authorized IP addresses or CIDR ranges for this tenant. + * * @return the set of authorized IP addresses/ranges */ public Set getAuthorizedIPs() { @@ -242,6 +255,7 @@ public Set getAuthorizedIPs() { /** * Sets the authorized IP addresses or CIDR ranges for this tenant. + * * @param authorizedIPs the set of authorized IP addresses/ranges to set */ public void setAuthorizedIPs(Set authorizedIPs) { @@ -254,6 +268,7 @@ public void setAuthorizedIPs(Set authorizedIPs) { * non-revoked, non-expired private key, but since the plaintext key is never persisted (see * UNOMI-938), this cannot be used to authenticate as the tenant; the real plaintext key is * only available once, at creation time, via {@link ApiKeyCreationResult}. + * * @return the masked private API key, or null if no valid private key exists */ @XmlTransient @@ -267,6 +282,7 @@ public String getPrivateApiKey() { * non-revoked, non-expired public key, but since the plaintext key is never persisted (see * UNOMI-938), this cannot be used in client-side applications; the real plaintext key is * only available once, at creation time, via {@link ApiKeyCreationResult}. + * * @return the masked public API key, or null if no valid public key exists */ @XmlTransient @@ -277,6 +293,7 @@ public String getPublicApiKey() { /** * Gets all active private API keys for the tenant. * This method returns all non-revoked, non-expired private keys. + * * @return list of active private API keys, or empty list if none exist */ @XmlTransient @@ -287,6 +304,7 @@ public List getActivePrivateApiKeys() { /** * Gets all active public API keys for the tenant. * This method returns all non-revoked, non-expired public keys. + * * @return list of active public API keys, or empty list if none exist */ @XmlTransient @@ -297,6 +315,7 @@ public List getActivePublicApiKeys() { /** * Gets all active API keys for the tenant. * This method returns all non-revoked, non-expired keys regardless of type. + * * @return list of all active API keys, or empty list if none exist */ @XmlTransient diff --git a/api/src/main/java/org/apache/unomi/api/tenants/TenantAuditService.java b/api/src/main/java/org/apache/unomi/api/tenants/TenantAuditService.java index 261a36e23..4c6fac1e1 100644 --- a/api/src/main/java/org/apache/unomi/api/tenants/TenantAuditService.java +++ b/api/src/main/java/org/apache/unomi/api/tenants/TenantAuditService.java @@ -17,7 +17,8 @@ package org.apache.unomi.api.tenants; /** - * A service to audit tenant-related operations. + * Records administrative actions performed in a tenant context. + * Complements item audit with tenant-scoped security and operations history. */ public interface TenantAuditService { /** @@ -27,4 +28,4 @@ public interface TenantAuditService { * @param operation the operation being performed */ void logTenantOperation(String tenantId, String operation); -} \ No newline at end of file +} \ No newline at end of file diff --git a/api/src/main/java/org/apache/unomi/api/tenants/TenantBackupMetadata.java b/api/src/main/java/org/apache/unomi/api/tenants/TenantBackupMetadata.java index 4cae6cfba..0c9740e8a 100644 --- a/api/src/main/java/org/apache/unomi/api/tenants/TenantBackupMetadata.java +++ b/api/src/main/java/org/apache/unomi/api/tenants/TenantBackupMetadata.java @@ -16,23 +16,51 @@ */ package org.apache.unomi.api.tenants; +/** + * Bookmark for a tenant backup operation. + * Associates a {@code tenantId} with the {@code timestamp} when backup metadata + * was recorded so restore and housekeeping jobs can tell which backup generation + * applies to which tenant. + */ public class TenantBackupMetadata { + /** Tenant that owns this backup record. */ private String tenantId; + /** When this backup metadata was recorded (milliseconds since epoch). */ private long timestamp; + /** + * Tenant that owns this backup. + * + * @return tenant id + */ public String getTenantId() { return tenantId; } + /** + * Sets the tenant id. + * + * @param tenantId tenant id + */ public void setTenantId(String tenantId) { this.tenantId = tenantId; } + /** + * When this backup metadata was recorded. + * + * @return timestamp in milliseconds since epoch + */ public long getTimestamp() { return timestamp; } + /** + * Sets the backup metadata timestamp. + * + * @param timestamp timestamp in milliseconds since epoch + */ public void setTimestamp(long timestamp) { this.timestamp = timestamp; } -} \ No newline at end of file +} \ No newline at end of file diff --git a/api/src/main/java/org/apache/unomi/api/tenants/TenantEventPurgeResult.java b/api/src/main/java/org/apache/unomi/api/tenants/TenantEventPurgeResult.java index 7199a64b1..eed555a7b 100644 --- a/api/src/main/java/org/apache/unomi/api/tenants/TenantEventPurgeResult.java +++ b/api/src/main/java/org/apache/unomi/api/tenants/TenantEventPurgeResult.java @@ -17,7 +17,9 @@ package org.apache.unomi.api.tenants; /** - * Result of a tenant-scoped event retention purge request. + * Outcome of a request to delete old events for one tenant. + * Reports how many events matched the retention cutoff and whether the + * delete-by-query completed successfully. */ public class TenantEventPurgeResult { @@ -34,42 +36,100 @@ public class TenantEventPurgeResult { private boolean purgeRequested; private long requestedAt; + /** + * Tenant this purge result applies to. + * + * @return tenant id + */ public String getTenantId() { return tenantId; } + /** + * Sets the unique identifier of the tenant associated with this result. + * + * @param tenantId The tenant ID to set. + */ public void setTenantId(String tenantId) { this.tenantId = tenantId; } + /** + * Retention window in days before events become eligible for purge. + * + * @return retention period in days + */ public int getRetentionDays() { return retentionDays; } + /** + * Sets the number of days events must be retained before + * purge consideration. + * + * @param retentionDays The retention period in days. + */ public void setRetentionDays(int retentionDays) { this.retentionDays = retentionDays; } + /** + * Estimated event count matching the retention cutoff before delete-by-query ran. + * This is a pre-delete estimate, not a post-delete count. + * + * @return estimated matching event count + */ public long getEventsMatched() { return eventsMatched; } + /** + * Sets the estimated number of events that matched the retention cutoff + * before the delete-by-query was submitted; not a post-delete count. + * + * @param eventsMatched The estimated count of matching events. + */ public void setEventsMatched(long eventsMatched) { this.eventsMatched = eventsMatched; } + /** + * Checks whether the delete-by-query for event purging completed + * successfully. + * + * @return {@code true} if the delete-by-query completed successfully, + * {@code false} if the persistence layer reported a failure + */ public boolean isPurgeRequested() { return purgeRequested; } + /** + * Sets whether the delete-by-query for event purging completed + * successfully. + * + * @param purgeRequested {@code true} if the delete-by-query completed + * successfully, {@code false} otherwise + */ public void setPurgeRequested(boolean purgeRequested) { this.purgeRequested = purgeRequested; } + /** + * Gets the timestamp (in milliseconds) when this event retention purge + * request was initiated. + * + * @return The requested time in milliseconds since the epoch. + */ public long getRequestedAt() { return requestedAt; } + /** + * Sets the timestamp when the event retention purge request was initiated. + * + * @param requestedAt the time in milliseconds since the epoch + */ public void setRequestedAt(long requestedAt) { this.requestedAt = requestedAt; } diff --git a/api/src/main/java/org/apache/unomi/api/tenants/TenantScopeUsage.java b/api/src/main/java/org/apache/unomi/api/tenants/TenantScopeUsage.java index 3029b1fb4..057240412 100644 --- a/api/src/main/java/org/apache/unomi/api/tenants/TenantScopeUsage.java +++ b/api/src/main/java/org/apache/unomi/api/tenants/TenantScopeUsage.java @@ -17,7 +17,10 @@ package org.apache.unomi.api.tenants; /** - * Segment and rule counts for one scope within a tenant. + * Per-scope slice of a {@link TenantUsage} snapshot. + * When usage collection runs, each scope contributes segment and rule counts so + * operators can see which sites or applications consume the most configuration + * inside a tenant. */ public class TenantScopeUsage { @@ -25,26 +28,56 @@ public class TenantScopeUsage { private long segmentCount; private long ruleCount; + /** + * Scope identifier. + * + * @return scope id + */ public String getScopeId() { return scopeId; } + /** + * Sets the scope identifier. + * + * @param scopeId scope id + */ public void setScopeId(String scopeId) { this.scopeId = scopeId; } + /** + * Number of segments in this scope. + * + * @return segment count + */ public long getSegmentCount() { return segmentCount; } + /** + * Sets the segment count. + * + * @param segmentCount segment count + */ public void setSegmentCount(long segmentCount) { this.segmentCount = segmentCount; } + /** + * Number of rules in this scope. + * + * @return rule count + */ public long getRuleCount() { return ruleCount; } + /** + * Sets the rule count. + * + * @param ruleCount rule count + */ public void setRuleCount(long ruleCount) { this.ruleCount = ruleCount; } diff --git a/api/src/main/java/org/apache/unomi/api/tenants/TenantStatus.java b/api/src/main/java/org/apache/unomi/api/tenants/TenantStatus.java index aad239918..021f71b6e 100644 --- a/api/src/main/java/org/apache/unomi/api/tenants/TenantStatus.java +++ b/api/src/main/java/org/apache/unomi/api/tenants/TenantStatus.java @@ -17,8 +17,8 @@ package org.apache.unomi.api.tenants; /** - * Enumeration of possible tenant statuses. - * This enum defines the various states a tenant can be in within the system. + * Lifecycle state of a tenant (for example active, suspended, or pending). + * Administrators filter and manage tenants based on these enum values. */ public enum TenantStatus { /** diff --git a/api/src/main/java/org/apache/unomi/api/tenants/TenantUsage.java b/api/src/main/java/org/apache/unomi/api/tenants/TenantUsage.java index 5b78ac10d..9313ecd8e 100644 --- a/api/src/main/java/org/apache/unomi/api/tenants/TenantUsage.java +++ b/api/src/main/java/org/apache/unomi/api/tenants/TenantUsage.java @@ -23,7 +23,6 @@ /** * Read-only usage snapshot for a tenant. Values are refreshed on a background schedule * (see {@link TenantUsageService}) and may be stale until the next collection cycle. - * *

Profile, scope, segment, and rule counts are point-in-time totals. {@link #eventCount} * counts events whose {@code timeStamp} falls within {@link #periodStart} (inclusive) and * {@link #periodEnd} (exclusive).

@@ -53,116 +52,268 @@ public class TenantUsage { private List scopeUsages = new ArrayList<>(); private long collectedAt; + /** + * Tenant this usage snapshot describes. + * + * @return tenant id + */ public String getTenantId() { return tenantId; } + /** + * Sets the unique identifier for the tenant associated with + * this usage snapshot. + * + * @param tenantId The ID of the tenant. + */ public void setTenantId(String tenantId) { this.tenantId = tenantId; } + /** + * Returns the normalized period label, e.g., {@code 2026-07} + * for a calendar month. + * + * @return The period label string. + */ public String getPeriod() { return period; } + /** + * Sets the normalized period label, e.g., {@code 2026-07} + * for a calendar month. + * + * @param period The desired period label. + */ public void setPeriod(String period) { this.period = period; } + /** + * Returns the inclusive start of the reporting period (epoch millis, UTC). + * + * @return The starting epoch millisecond value. + */ public long getPeriodStart() { return periodStart; } + /** + * Sets the inclusive start of the reporting period (epoch millis, UTC). + * + * @param periodStart The starting epoch millisecond value. + */ public void setPeriodStart(long periodStart) { this.periodStart = periodStart; } + /** + * Returns the exclusive end of the reporting period (epoch millis, UTC). + * + * @return The ending epoch millisecond value. + */ public long getPeriodEnd() { return periodEnd; } + /** + * Sets the exclusive end of the reporting period (epoch millis, UTC). + * + * @param periodEnd The ending epoch millisecond value. + */ public void setPeriodEnd(long periodEnd) { this.periodEnd = periodEnd; } + /** + * Total profile count at collection time. + * + * @return profile count + */ public long getProfileCount() { return profileCount; } + /** + * Sets the number of profiles for this tenant usage snapshot. + * + * @param profileCount The total count of profiles. + */ public void setProfileCount(long profileCount) { this.profileCount = profileCount; } + /** + * Returns the number of events counted for this tenant in the reporting + * period. Events are included when their {@code timeStamp} falls within + * {@link #getPeriodStart()} (inclusive) and {@link #getPeriodEnd()} + * (exclusive). + * + * @return the event count for the reporting period + */ public long getEventCount() { return eventCount; } + /** + * Sets the count of events for this tenant usage snapshot. + * + * @param eventCount The total number of events. + */ public void setEventCount(long eventCount) { this.eventCount = eventCount; } + /** + * Tenant scope count excluding {@code systemscope}. + * + * @return scope count + */ public long getScopeCount() { return scopeCount; } + /** + * Sets the count of tenant scopes excluding {@code systemscope}. + * + * @param scopeCount The total number of scopes. + */ public void setScopeCount(long scopeCount) { this.scopeCount = scopeCount; } + /** + * Segment count at collection time. + * + * @return segment count + */ public long getSegmentCount() { return segmentCount; } + /** + * Sets the count of segments for this tenant usage snapshot. + * + * @param segmentCount The total number of segments. + */ public void setSegmentCount(long segmentCount) { this.segmentCount = segmentCount; } + /** + * Rule count at collection time. + * + * @return rule count + */ public long getRuleCount() { return ruleCount; } + /** + * Sets the count of rules associated with this tenant usage snapshot. + * + * @param ruleCount The total number of rules. + */ public void setRuleCount(long ruleCount) { this.ruleCount = ruleCount; } + /** + * Document count across all tenant indices at collection time. + * + * @return total document count + */ public long getStorageDocumentCount() { return storageDocumentCount; } + /** + * Sets the document count across all tenant indices + * recorded in this snapshot. + * + * @param storageDocumentCount The total number of documents. + */ public void setStorageDocumentCount(long storageDocumentCount) { this.storageDocumentCount = storageDocumentCount; } + /** + * Active API key count on the tenant record. + * + * @return active API key count + */ public long getActiveApiKeyCount() { return activeApiKeyCount; } + /** + * Sets the count of active API keys on the tenant record + * for this usage period. + * + * @param activeApiKeyCount The total number of active API keys. + */ public void setActiveApiKeyCount(long activeApiKeyCount) { this.activeApiKeyCount = activeApiKeyCount; } + /** + * In-memory REST request counter for this tenant since process start. + * + * @return REST request count + */ public long getRestRequestCount() { return restRequestCount; } + /** + * Sets the in-memory REST request counter for this tenant since the + * Unomi process started. + * + * @param restRequestCount The total number of REST requests recorded. + */ public void setRestRequestCount(long restRequestCount) { this.restRequestCount = restRequestCount; } + /** + * Per-scope usage breakdown for this snapshot. + * + * @return unmodifiable scope usage list + */ public List getScopeUsages() { return Collections.unmodifiableList(scopeUsages); } + /** + * Sets the list of usage details for all tenant scopes. This method + * performs a defensive copy of the provided list to ensure + * internal state immutability. + * + * @param scopeUsages The list of {@link TenantScopeUsage} objects. + */ public void setScopeUsages(List scopeUsages) { // Copy defensively: callers (including the internal usage cache) must not be able to // mutate this snapshot's state through a shared list reference after construction. this.scopeUsages = scopeUsages != null ? new ArrayList<>(scopeUsages) : new ArrayList<>(); } + /** + * Gets the timestamp (epoch millis) when this usage snapshot was collected. + * + * @return The collection time in epoch milliseconds. + */ public long getCollectedAt() { return collectedAt; } + /** + * Sets the timestamp (epoch millis) indicating when this usage + * snapshot was collected. + * + * @param collectedAt The collection time in epoch milliseconds. + */ public void setCollectedAt(long collectedAt) { this.collectedAt = collectedAt; } diff --git a/api/src/main/java/org/apache/unomi/api/tenants/TenantUsageService.java b/api/src/main/java/org/apache/unomi/api/tenants/TenantUsageService.java index 789584683..df44ed56b 100644 --- a/api/src/main/java/org/apache/unomi/api/tenants/TenantUsageService.java +++ b/api/src/main/java/org/apache/unomi/api/tenants/TenantUsageService.java @@ -43,6 +43,8 @@ public interface TenantUsageService { /** * Records one authenticated REST request for the tenant (in-memory counter). + * + * @param tenantId tenant identifier */ void recordRestRequest(String tenantId); diff --git a/api/src/main/java/org/apache/unomi/api/tenants/security/SecurityAuditReport.java b/api/src/main/java/org/apache/unomi/api/tenants/security/SecurityAuditReport.java index eb1215bfc..8fba8c63a 100644 --- a/api/src/main/java/org/apache/unomi/api/tenants/security/SecurityAuditReport.java +++ b/api/src/main/java/org/apache/unomi/api/tenants/security/SecurityAuditReport.java @@ -21,9 +21,10 @@ import java.util.Map; /** - * Represents a security audit report for a tenant. - * This class contains information about security-related events and statistics - * within a specified time period. + * Security activity summary for one tenant over a time window. + * {@link TenantSecurityService} fills this report with authentication events, + * per-type counts, and aggregate statistics so operators can review access + * patterns and failed attempts for a given period. */ public class SecurityAuditReport { private String tenantId; @@ -34,96 +35,108 @@ public class SecurityAuditReport { private Map statistics; /** - * Gets the tenant ID associated with this report. - * @return the tenant ID + * Tenant covered by this audit report. + * + * @return tenant id */ public String getTenantId() { return tenantId; } /** - * Sets the tenant ID associated with this report. - * @param tenantId the tenant ID to set + * Sets the tenant id for this audit report. + * + * @param tenantId tenant id */ public void setTenantId(String tenantId) { this.tenantId = tenantId; } /** - * Gets the start date of the audit period. - * @return the start date + * Inclusive start of the audit window. + * + * @return start date */ public Date getStartDate() { return startDate; } /** - * Sets the start date of the audit period. - * @param startDate the start date to set + * Sets the inclusive start of the audit window. + * + * @param startDate start date */ public void setStartDate(Date startDate) { this.startDate = startDate; } /** - * Gets the end date of the audit period. - * @return the end date + * Exclusive end of the audit window. + * + * @return end date */ public Date getEndDate() { return endDate; } /** - * Sets the end date of the audit period. - * @param endDate the end date to set + * Sets the exclusive end of the audit window. + * + * @param endDate end date */ public void setEndDate(Date endDate) { this.endDate = endDate; } /** - * Gets the list of security events. - * @return list of security events + * Security events recorded in the audit window. + * + * @return security events */ public List getEvents() { return events; } /** - * Sets the list of security events. - * @param events list of security events to set + * Sets the security events for this report. + * + * @param events security events */ public void setEvents(List events) { this.events = events; } /** - * Gets the count of events by type. - * @return map of event types to their counts + * Event counts grouped by event type. + * + * @return map of event type to count */ public Map getEventCounts() { return eventCounts; } /** - * Sets the count of events by type. - * @param eventCounts map of event types to their counts + * Sets event counts grouped by event type. + * + * @param eventCounts map of event type to count */ public void setEventCounts(Map eventCounts) { this.eventCounts = eventCounts; } /** - * Gets additional statistics about the audit period. - * @return map of statistics + * Aggregate statistics for the audit window. + * + * @return statistics map */ public Map getStatistics() { return statistics; } /** - * Sets additional statistics about the audit period. - * @param statistics map of statistics to set + * Sets aggregate statistics for the audit window. + * + * @param statistics statistics map */ public void setStatistics(Map statistics) { this.statistics = statistics; @@ -140,52 +153,105 @@ public static class SecurityEvent { private String ipAddress; private Map details; + /** + * Security event type identifier. + * + * @return event type + */ public String getType() { return type; } + /** + * Sets the type identifier for this security event. + * @param type The type string to set. + */ public void setType(String type) { this.type = type; } + /** + * When the security event occurred. + * + * @return event timestamp + */ public Date getTimestamp() { return timestamp; } + /** + * Sets the timestamp for this security event. + * @param timestamp The {@link java.util.Date} to set as + * the event timestamp. + */ public void setTimestamp(Date timestamp) { this.timestamp = timestamp; } + /** + * Human-readable description of the security event. + * + * @return event description + */ public String getDescription() { return description; } + /** + * Sets the descriptive text for this security event. + * @param description The description string to set. + */ public void setDescription(String description) { this.description = description; } + /** + * User associated with the security event. + * + * @return user id + */ public String getUserId() { return userId; } + /** + * Sets the user ID associated with this security event. + * @param userId The user ID to set. + */ public void setUserId(String userId) { this.userId = userId; } + /** + * Gets the IP address associated with this security event. + * @return The stored {@link String} IP address. + */ public String getIpAddress() { return ipAddress; } + /** + * Sets the IP address associated with this security event. + * @param ipAddress the IP address to set. + */ public void setIpAddress(String ipAddress) { this.ipAddress = ipAddress; } + /** + * Gets a map containing additional details about the security event. + * @return The {@link java.util.Map} of details. + */ public Map getDetails() { return details; } + /** + * Sets a map containing additional details about the security event. + * @param details the map of details to set. + */ public void setDetails(Map details) { this.details = details; } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/api/src/main/java/org/apache/unomi/api/tenants/security/SecuritySettings.java b/api/src/main/java/org/apache/unomi/api/tenants/security/SecuritySettings.java index ade4913db..118172bb4 100644 --- a/api/src/main/java/org/apache/unomi/api/tenants/security/SecuritySettings.java +++ b/api/src/main/java/org/apache/unomi/api/tenants/security/SecuritySettings.java @@ -20,9 +20,9 @@ import java.util.Map; /** - * Represents security settings for a tenant. - * This class contains configuration for various security aspects including - * authentication, authorization, and API access. + * Tenant-level security policy loaded and enforced by {@link TenantSecurityService}. + * Groups authentication rules (token/session settings), authorization mappings, + * rate limits, and API access constraints that apply to REST calls for one tenant. */ public class SecuritySettings { private boolean enabled; @@ -31,64 +31,72 @@ public class SecuritySettings { private Map additionalSettings; /** - * Gets whether security is enabled for the tenant. - * @return true if security is enabled, false otherwise + * Whether tenant security enforcement is enabled. + * + * @return {@code true} when security is enabled */ public boolean isEnabled() { return enabled; } /** - * Sets whether security is enabled for the tenant. - * @param enabled true to enable security, false to disable + * Enables or disables tenant security enforcement. + * + * @param enabled {@code true} to enable security */ public void setEnabled(boolean enabled) { this.enabled = enabled; } /** - * Gets the authentication configuration. - * @return the authentication configuration + * Authentication policy for this tenant. + * + * @return authentication configuration */ public AuthenticationConfig getAuthentication() { return authentication; } /** - * Sets the authentication configuration. - * @param authentication the authentication configuration to set + * Sets the authentication policy for this tenant. + * + * @param authentication authentication configuration */ public void setAuthentication(AuthenticationConfig authentication) { this.authentication = authentication; } /** - * Gets the authorization configuration. - * @return the authorization configuration + * Authorization policy for this tenant. + * + * @return authorization configuration */ public AuthorizationConfig getAuthorization() { return authorization; } /** - * Sets the authorization configuration. - * @param authorization the authorization configuration to set + * Sets the authorization policy for this tenant. + * + * @param authorization authorization configuration */ public void setAuthorization(AuthorizationConfig authorization) { this.authorization = authorization; } /** - * Gets additional security settings as key-value pairs. - * @return map of additional settings + * Extra security settings not covered by authentication or authorization. + * + * @return additional settings map */ public Map getAdditionalSettings() { return additionalSettings; } /** - * Sets additional security settings as key-value pairs. - * @param additionalSettings map of additional settings to set + * Sets extra security settings not covered by authentication or authorization. + * + * @param additionalSettings additional settings map */ public void setAdditionalSettings(Map additionalSettings) { this.additionalSettings = additionalSettings; @@ -103,34 +111,72 @@ public static class AuthenticationConfig { private int lockoutDurationMinutes; private boolean requireMfa; + /** + * Gets the list of allowed authentication methods. + * @return A {@link java.util.List} of {@link String} representing the + * allowed auth methods. + */ public List getAllowedAuthMethods() { return allowedAuthMethods; } + /** + * Sets the list of allowed authentication methods for the tenant. + * @param allowedAuthMethods The list of allowed authentication methods. + */ public void setAllowedAuthMethods(List allowedAuthMethods) { this.allowedAuthMethods = allowedAuthMethods; } + /** + * Maximum failed login attempts before account lockout. + * + * @return max login attempts + */ public int getMaxLoginAttempts() { return maxLoginAttempts; } + /** + * Sets the maximum number of consecutive failed login attempts allowed. + * @param maxLoginAttempts The maximum number of login attempts. + */ public void setMaxLoginAttempts(int maxLoginAttempts) { this.maxLoginAttempts = maxLoginAttempts; } + /** + * Gets the duration, in minutes, that an account remains locked after + * exceeding login attempts. + * @return The lockout duration in minutes. + */ public int getLockoutDurationMinutes() { return lockoutDurationMinutes; } + /** + * Sets the duration (in minutes) for which a user account will be + * locked out upon failed logins. + * @param lockoutDurationMinutes The desired lockout + * duration in minutes. + */ public void setLockoutDurationMinutes(int lockoutDurationMinutes) { this.lockoutDurationMinutes = lockoutDurationMinutes; } + /** + * Checks if multi-factor authentication is required for login. + * @return {@code true} if MFA is required, {@code false} otherwise. + */ public boolean isRequireMfa() { return requireMfa; } + /** + * Sets whether multi-factor authentication must be used during login. + * @param requireMfa If {@code true}, MFA is mandatory; + * otherwise, it is optional. + */ public void setRequireMfa(boolean requireMfa) { this.requireMfa = requireMfa; } @@ -144,28 +190,56 @@ public static class AuthorizationConfig { private List permissions; private Map> rolePermissions; + /** + * Roles defined for this tenant. + * + * @return role names + */ public List getRoles() { return roles; } + /** + * Sets the list of roles for this tenant. + * @param roles The list of role names to set. + */ public void setRoles(List roles) { this.roles = roles; } + /** + * Permissions defined for this tenant. + * + * @return permission names + */ public List getPermissions() { return permissions; } + /** + * Sets the list of permissions for this tenant. + * @param permissions The list of permission names to set. + */ public void setPermissions(List permissions) { this.permissions = permissions; } + /** + * Maps each role to the permissions it grants. + * + * @return role-to-permissions map + */ public Map> getRolePermissions() { return rolePermissions; } + /** + * Sets the mapping defining which permissions belong to specific roles. + * @param rolePermissions The map containing + * role-to-permission mappings to set. + */ public void setRolePermissions(Map> rolePermissions) { this.rolePermissions = rolePermissions; } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/api/src/main/java/org/apache/unomi/api/tenants/security/SecurityValidationResult.java b/api/src/main/java/org/apache/unomi/api/tenants/security/SecurityValidationResult.java index 88ed71ca1..427bb0f18 100644 --- a/api/src/main/java/org/apache/unomi/api/tenants/security/SecurityValidationResult.java +++ b/api/src/main/java/org/apache/unomi/api/tenants/security/SecurityValidationResult.java @@ -20,9 +20,10 @@ import java.util.List; /** - * Represents the result of a security validation operation. - * This class contains information about whether the validation was successful, - * and if not, what errors were encountered. + * Outcome of a tenant security validation step. + * Carries a success flag plus structured error messages when authentication, + * authorization, or rate-limit checks fail so REST layers can return actionable + * feedback without re-running the validation logic. */ public class SecurityValidationResult { private boolean valid; @@ -39,6 +40,7 @@ public SecurityValidationResult() { /** * Gets whether the validation was successful. + * * @return true if validation passed, false otherwise */ public boolean isValid() { @@ -47,6 +49,7 @@ public boolean isValid() { /** * Sets the validation status. + * * @param valid true if validation passed, false otherwise */ public void setValid(boolean valid) { @@ -55,6 +58,7 @@ public void setValid(boolean valid) { /** * Gets the list of validation errors. + * * @return list of error messages */ public List getErrors() { @@ -63,6 +67,7 @@ public List getErrors() { /** * Sets the list of validation errors. + * * @param errors list of error messages */ public void setErrors(List errors) { @@ -71,6 +76,7 @@ public void setErrors(List errors) { /** * Adds an error message to the result. + * * @param error the error message to add */ public void addError(String error) { @@ -80,6 +86,7 @@ public void addError(String error) { /** * Gets the general message associated with the validation result. + * * @return the message */ public String getMessage() { @@ -88,6 +95,7 @@ public String getMessage() { /** * Sets the general message associated with the validation result. + * * @param message the message to set */ public void setMessage(String message) { 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 e9116d3a0..d2b051279 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 @@ -19,9 +19,10 @@ import javax.servlet.http.HttpServletRequest; /** - * Service interface for managing tenants-level security operations and validations. - * This service provides comprehensive security features including authentication, - * authorization, rate limiting, and security auditing for tenants-specific operations. + * Entry point for tenant-scoped REST security checks. + * Validates incoming HTTP requests against {@link SecuritySettings}, enforces + * rate limits, applies authorization rules, and can produce + * {@link SecurityAuditReport} data for operator review. */ public interface TenantSecurityService { diff --git a/api/src/main/java/org/apache/unomi/api/utils/ParserHelper.java b/api/src/main/java/org/apache/unomi/api/utils/ParserHelper.java index 0ab6f08d6..152dc4b74 100644 --- a/api/src/main/java/org/apache/unomi/api/utils/ParserHelper.java +++ b/api/src/main/java/org/apache/unomi/api/utils/ParserHelper.java @@ -37,7 +37,9 @@ import java.util.concurrent.ConcurrentHashMap; /** - * Helper class to resolve condition, action and values types when loading definitions from JSON files + * Utilities for resolving condition, action, and value type references when + * importing JSON definitions. Walks condition trees, fills in missing type + * metadata, and extracts event types referenced by conditions. */ public class ParserHelper { @@ -54,15 +56,67 @@ public class ParserHelper { private static final int MAX_RECURSION_DEPTH = 1000; + /** + * A visitor implementation used by {@link ParserHelper} to traverse and + * process {@link Condition} nodes found within parsed + * definitions (e.g., from JSON). + * Subclasses must implement the required methods to define how conditions + * are visited upon entry and exited upon completion of processing. + */ public interface ConditionVisitor { + /** + * Visits a given {@link Condition} object, executing necessary logic + * for that specific condition during traversal. + * + * @param condition The condition being visited. + */ void visit(Condition condition); + /** + * Executes post-visit logic for the specified {@link Condition} object. + * This method is called after all descendants of the given condition + * have been processed. + * + * @param condition The condition whose children have + * finished processing. + */ void postVisit(Condition condition); } + /** + * Utility class responsible for extracting and converting raw string values + * found in configuration definitions (such as conditions or actions) into + * usable Java objects. + * This abstract helper requires subclasses to implement the core logic of + * value extraction, utilizing both the {@code String} representation of the + * value and the associated {@link Event} context for resolution. + */ public interface ValueExtractor { + /** + * Extracts an object value from a given string representation using the + * provided event context. + * This method is abstract and must be implemented by + * concrete subclasses. + * + * @param valueAsString The string value that needs to be + * extracted or parsed. + * + * @param event The {@link Event} providing context for + * the extraction process. + * + * @return An object representing the extracted value. + * @throws IllegalAccessException if access to a required member fails. + * @throws NoSuchMethodException if a necessary method cannot be found. + * @throws InvocationTargetException if an exception occurs during + * invocation of the target method. + */ Object extract(String valueAsString, Event event) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException; } + /** + * Default map containing predefined {@link ValueExtractor} implementations. + * These extractors are used to retrieve values from various sources + * (profile, session, event) when processing definitions. + */ public static final Map DEFAULT_VALUE_EXTRACTORS = new HashMap<>(); static { DEFAULT_VALUE_EXTRACTORS.put("profileProperty", (valueAsString, event) -> PropertyUtils.getProperty(event.getProfile(), "properties." + valueAsString)); @@ -73,6 +127,21 @@ public interface ValueExtractor { DEFAULT_VALUE_EXTRACTORS.put("simpleEventProperty", (valueAsString, event) -> event.getProperty(valueAsString)); } + /** + * Resolves the condition types recursively starting from a root condition. + * This method traverses the parent chain and parameter values of the given + * {@code rootCondition} to ensure all nested conditions have their types + * resolved against the provided definitions service. It updates the + * condition type on the condition object if successful. + * + * @param definitionsService service used to resolve condition types. + * @param rootCondition the starting condition for resolution. + * @param contextObjectName name of the object providing context + * (for error messages). + * + * @return {@code true} if all reachable conditions were successfully resolved, + * {@code false} otherwise + */ public static boolean resolveConditionType(final DefinitionsService definitionsService, Condition rootCondition, String contextObjectName) { return resolveConditionType(definitionsService, rootCondition, contextObjectName, new HashSet<>(), false, 0); @@ -158,14 +227,31 @@ private static boolean resolveConditionType(final DefinitionsService definitions } } + /** + * Collects a list of unique condition type IDs present within the + * given root condition. + * This method traverses the entire structure of the condition, including + * nested parameters and collections, and gathers all associated + * condition type ids. + * + * @param rootCondition the condition whose types are to be collected. + * @return a list of unique strings representing the condition type IDs + * found in the condition tree. + */ public static List getConditionTypeIds(Condition rootCondition) { final List result = new ArrayList(); visitConditions(rootCondition, new ConditionVisitor() { + /** + * {@inheritDoc} + */ @Override public void visit(Condition condition) { result.add(condition.getConditionTypeId()); } + /** + * {@inheritDoc} + */ @Override public void postVisit(Condition condition) { } @@ -173,6 +259,16 @@ public void postVisit(Condition condition) { return result; } + /** + * Visits every condition within the given condition structure, executing + * the provided visitor for each one. This method handles recursive + * traversal through parameters and collections that may + * contain nested conditions. + * + * @param rootCondition the starting condition to visit. + * @param visitor the {@link ConditionVisitor} implementation to execute on + * every visited condition. + */ public static void visitConditions(Condition rootCondition, ConditionVisitor visitor) { visitor.visit(rootCondition); // recursive call for sub-conditions as parameters @@ -194,6 +290,22 @@ public static void visitConditions(Condition rootCondition, ConditionVisitor vis visitor.postVisit(rootCondition); } + /** + * Resolves action types for all actions contained within a given rule. + * This method checks if the rule has null or empty actions. If not, it + * attempts to resolve the type of each action using the + * {@link DefinitionsService}. It logs warnings if resolution fails and + * returns {@code false} if any structural error (null/empty actions) or + * resolution failure occurs. + * + * @param definitionsService service used to resolve action types. + * @param rule the rule containing actions whose types need resolving. + * @param ignoreErrors if {@code true}, warnings about missing or empty + * actions are suppressed. + * + * @return {@code true} if all actions were successfully resolved, + * {@code false} otherwise. + */ public static boolean resolveActionTypes(DefinitionsService definitionsService, Rule rule, boolean ignoreErrors) { boolean result = true; String ruleId = rule.getItemId(); @@ -227,6 +339,17 @@ public static boolean resolveActionTypes(DefinitionsService definitionsService, return result; } + /** + * Attempts to resolve the action type for a single given action. + * If the action's type is not already set, this method queries the + * definitions service to find and set the correct {@link ActionType}. It + * tracks unresolved types internally, logging warnings if resolution fails. + * + * @param definitionsService service used to resolve action types. + * @param action the action whose type needs resolving. + * @return {@code true} if the action's type was successfully resolved or already set, + * {@code false} otherwise + */ public static boolean resolveActionType(DefinitionsService definitionsService, Action action) { if (definitionsService == null) { return false; @@ -247,6 +370,17 @@ public static boolean resolveActionType(DefinitionsService definitionsService, A return true; } + /** + * Resolves the value type for a given property type. + * This method first attempts to use the {@link DefinitionsService}'s + * internal {@link TypeResolutionService}. If that service is unavailable, + * it falls back to resolving the value type directly using the definitions + * service's lookup mechanism. + * The provided {@code PropertyType} object will be modified if successful. + * + * @param definitionsService service used to resolve property types. + * @param propertyType the property type whose value needs resolution. + */ public static void resolveValueType(DefinitionsService definitionsService, PropertyType propertyType) { if (definitionsService == null) { return; @@ -265,15 +399,28 @@ public static void resolveValueType(DefinitionsService definitionsService, Prope } } - /** * @deprecated Use {@link #resolveConditionEventTypes(Condition, DefinitionsService)} instead. + * + * @param rootCondition the condition whose event types are to be resolved + * @return a set of unique event type identifiers */ @Deprecated public static Set resolveConditionEventTypes(Condition rootCondition) { return resolveConditionEventTypes(rootCondition, null); } + /** + * Resolves all unique event types associated with a condition structure. + * This method traverses the entire condition tree starting from + * {@code rootCondition} and collects every distinct event type ID found in + * any nested condition or parameter. + * + * @param rootCondition the condition whose event types are to be resolved. + * @param definitionsService service used for resolving event types. + * @return a set of unique strings representing all event types + * associated with the condition. + */ public static Set resolveConditionEventTypes(Condition rootCondition, DefinitionsService definitionsService) { if (rootCondition == null) { return new HashSet<>(); @@ -283,16 +430,35 @@ public static Set resolveConditionEventTypes(Condition rootCondition, De return eventTypeConditionVisitor.getEventTypeIds(); } + /** + * A {@link ConditionVisitor} implementation used to traverse condition + * definitions within a rule or definition structure. It collects all unique + * event type IDs encountered during the traversal of conditions. + * This visitor maintains internal state, using a stack to track the current + * path of visited conditions and accumulating discovered {@code String} + * event type identifiers in a set. + */ public static class EventTypeConditionVisitor implements ConditionVisitor { private final DefinitionsService definitionsService; private Set eventTypeIds = new HashSet<>(); private Stack conditionTypeStack = new Stack<>(); + /** + * Constructs an {@code EventTypeConditionVisitor} and initializes it + * with a service used to resolve condition types. + * + * @param definitionsService The service providing definitions necessary + * for resolving type information. + */ public EventTypeConditionVisitor(DefinitionsService definitionsService) { this.definitionsService = definitionsService; } + /** + * {@inheritDoc} + */ + @Override public void visit(Condition condition) { conditionTypeStack.push(condition.getConditionTypeId()); @@ -344,15 +510,45 @@ public void visit(Condition condition) { } } + /** + * {@inheritDoc} + */ + @Override public void postVisit(Condition condition) { conditionTypeStack.pop(); } + /** + * Event type ids collected during condition-tree traversal. + * + * @return collected event type ids + */ public Set getEventTypeIds() { return eventTypeIds; } } + /** + * Parses a map of values, resolving any placeholders found within string + * values using provided extractors and event context. + * This method iterates through the input map. If a value is a String + * containing placeholders (e.g., ${placeholder}), it recursively resolves + * these placeholders by calling {@link #extractValue(String, Event, Map)}. + * The resulting map contains the processed values with resolved objects + * instead of placeholder strings. + * + * @param event The current event used for value + * extraction during resolution. + * + * @param map The input map containing potentially + * placeholder-filled values. + * + * @param valueExtractors A map linking value type names to their + * respective extractors. + * + * @return A new {@link Map} with the string placeholders + * resolved into actual object values. + */ @SuppressWarnings("unchecked") public static Map parseMap(Event event, Map map, Map valueExtractors) { Map values = new HashMap<>(); @@ -391,6 +587,33 @@ public static Map parseMap(Event event, Map map, return values; } + /** + * Extracts an object value from a string that follows a specific + * format (type::value). + * The method splits the input string using "::" to separate the expected + * value type from the raw value content. It then uses the provided + * {@link ValueExtractor} to perform the actual extraction based + * on the event context. + * + * @param s The input string containing the type and + * value separated by "::". + * + * @param event The current event used during value extraction. + * @param valueExtractors A map of available extractors + * keyed by value type name. + * + * @return The extracted object value, or null if no extractor is found + * or extraction fails. + * + * @throws IllegalAccessException If the underlying extractor + * throws this exception. + * + * @throws NoSuchMethodException If the underlying extractor + * throws this exception. + * + * @throws InvocationTargetException If the underlying extractor + * throws this exception. + */ public static Object extractValue(String s, Event event, Map valueExtractors) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException { Object value = null; @@ -409,6 +632,21 @@ public static Object extractValue(String s, Event event, Map values, Map valueExtractors) { for (Map.Entry entry : values.entrySet()) { @@ -434,7 +672,6 @@ public static boolean hasContextualParameter(Map values, Map collecti /** * Merges all fields from a Map into this builder. * This is useful for inheritance where subclasses want to include parent class fields. - * + * * Usage in subclasses: *
          * return YamlMapBuilder.create()
@@ -210,6 +210,7 @@ public Map build() {
     /**
      * Converts a Set to a sorted List for YAML output.
      *
+     * @param  the element type, which must be {@link Comparable}
      * @param set the set to convert
      * @return a sorted list, or null if the set is null or empty
      */
@@ -223,6 +224,8 @@ public static > List setToSortedList(Set set) {
     /**
      * Converts a Set to a sorted List using a mapper function.
      *
+     * @param  the source element type
+     * @param  the mapped element type, which must be {@link Comparable}
      * @param set the set to convert
      * @param mapper the mapper function (must not be null)
      * @return a sorted list, or null if the set is null or empty
@@ -292,7 +295,7 @@ public static Object toYamlValue(Object value, Set visited, int maxDepth
         if (value instanceof Map) {
             Map inputMap = (Map) value;
             Map result = new LinkedHashMap<>();
-            
+
             if (!inputMap.isEmpty()) {
                 // Sort entries alphabetically by key string representation
                 inputMap.entrySet().stream()
@@ -305,7 +308,6 @@ public static Object toYamlValue(Object value, Set visited, int maxDepth
         return value;
     }
 
-
     /**
      * Formats a value as YAML using SnakeYaml.
      * This is a convenience method that delegates to SnakeYaml.
diff --git a/build.sh b/build.sh
index d4dfb2f30..fd28ed1ff 100755
--- a/build.sh
+++ b/build.sh
@@ -326,7 +326,7 @@ EOF
         echo -e "  ${CYAN}--search-engine-logs${NC}       Stream search engine Docker logs to the Maven console during integration tests"
         echo -e "  ${CYAN}--no-memory-sampler${NC}        Disable JVM/system memory sampling during integration tests"
         echo -e "  ${CYAN}--memory-interval SEC${NC}    Memory sample interval in seconds (default: 30)"
-        echo -e "  ${CYAN}--javadoc${NC}                  Build and validate Javadoc after install (fails on doclint errors)"
+        echo -e "  ${CYAN}--javadoc${NC}                  Build and validate Javadoc after install (doclint errors fail; public/protected tag gaps warn)"
         echo -e "  ${CYAN}--ci${NC}                       CI mode: no Karaf, 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"
@@ -369,7 +369,7 @@ EOF
         echo "  --search-engine-logs      Stream search engine Docker logs to the Maven console during integration tests"
         echo "  --no-memory-sampler       Disable JVM/system memory sampling during integration tests"
         echo "  --memory-interval SEC     Memory sample interval in seconds (default: 30)"
-        echo "  --javadoc                 Build and validate Javadoc after install (fails on doclint errors)"
+        echo "  --javadoc                 Build and validate Javadoc after install (doclint errors fail; public/protected tag gaps warn)"
         echo "  --ci                      CI mode: no Karaf, 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"
@@ -1161,7 +1161,7 @@ echo "Estimated time: 3-5 minutes for build, 50-60 minutes with integration test
 start_timer
 
 # Build phases with enhanced output
-[ "$JAVADOC" = true ] && total_steps=3 || total_steps=2
+[ "$JAVADOC" = true ] && total_steps=4 || total_steps=2
 current_step=0
 
 write_it_run_trace_start() {
@@ -1276,7 +1276,7 @@ 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..."
+    print_progress $((++current_step)) $total_steps "Generating Javadoc (doclint: links, HTML, syntax)..."
     if [ "$HAS_COLORS" -eq 1 ]; then
         echo -e "${GRAY}Running: $MVN_CMD javadoc:javadoc -DskipTests $MVN_OPTS${NC}"
     else
@@ -1286,7 +1286,19 @@ if [ "$JAVADOC" = true ]; then
         print_status "error" "Javadoc validation failed — fix doclint errors above before pushing"
         exit 1
     }
-    print_status "success" "Javadoc validated successfully"
+    print_status "success" "Javadoc doclint passed (no broken links, HTML, or syntax errors)"
+
+    print_progress $((++current_step)) $total_steps "Checking public/protected Javadoc tags (warnings only)..."
+    if [ "$HAS_COLORS" -eq 1 ]; then
+        echo -e "${GRAY}Running: $MVN_CMD -Pjavadoc-tags-warn checkstyle:check -DskipTests $MVN_OPTS${NC}"
+    else
+        echo "Running: $MVN_CMD -Pjavadoc-tags-warn checkstyle:check -DskipTests $MVN_OPTS"
+    fi
+    $MVN_CMD -Pjavadoc-tags-warn checkstyle:check -DskipTests $MVN_OPTS || {
+        print_status "error" "Javadoc tag check failed unexpectedly — see output above"
+        exit 1
+    }
+    print_status "success" "Javadoc tag check completed (review any warnings above)"
 fi
 
 # Deployment section with enhanced output
diff --git a/common/src/main/java/org/apache/unomi/common/DataTable.java b/common/src/main/java/org/apache/unomi/common/DataTable.java
index 21862d299..5ec39d6e7 100644
--- a/common/src/main/java/org/apache/unomi/common/DataTable.java
+++ b/common/src/main/java/org/apache/unomi/common/DataTable.java
@@ -35,13 +35,26 @@ public class DataTable {
 
     public static final EmptyCell EMPTY_CELL = new EmptyCell();
 
+    /**
+     * Creates an empty data table.
+     */
     public DataTable() {
     }
 
+    /**
+     * Returns the rows stored in this table.
+     *
+     * @return the table rows
+     */
     public List getRows() {
         return rows;
     }
 
+    /**
+     * Appends a row built from the given cell values.
+     *
+     * @param rowData cell values for the new row
+     */
     public void addRow(Comparable... rowData) {
         if (rowData == null) {
             return;
@@ -56,32 +69,63 @@ public void addRow(Comparable... rowData) {
         rows.add(row);
     }
 
+    /**
+     * Returns the maximum number of columns across all rows.
+     *
+     * @return the maximum column count
+     */
     public int getMaxColumns() {
         return maxColumns;
     }
 
+    /**
+     * Sort order used when comparing row values.
+     */
     public static enum SortOrder {
         ASCENDING,
         DESCENDING;
     }
 
+    /**
+     * Defines a column index and sort order for table sorting.
+     */
     public static class SortCriteria {
         Integer columnIndex;
         SortOrder sortOrder;
 
+        /**
+         * Creates a sort criterion for one column.
+         *
+         * @param columnIndex zero-based column index
+         * @param sortOrder sort direction for the column
+         */
         public SortCriteria(Integer columnIndex, SortOrder sortOrder) {
             this.columnIndex = columnIndex;
             this.sortOrder = sortOrder;
         }
     }
 
+    /**
+     * One row of comparable cell values in a {@link DataTable}.
+     */
     public class Row {
         List rowData = new ArrayList<>();
 
+        /**
+         * Appends a cell value to this row.
+         *
+         * @param data the cell value to add
+         */
         public void addData(Comparable data) {
             rowData.add(data);
         }
 
+        /**
+         * Returns the cell value at the given index.
+         *
+         * @param index zero-based column index
+         * @return the cell value, or {@link #EMPTY_CELL} when the row has no value at that index
+         */
         public Comparable getData(int index) {
             if (index >= maxColumns) {
                 throw new ArrayIndexOutOfBoundsException("Index on row data (" + index + ") is larger than max columns (" + maxColumns + ")");
@@ -101,6 +145,11 @@ public String toString() {
         }
     }
 
+    /**
+     * Sorts table rows using the given criteria in order.
+     *
+     * @param sortCriterias column sort criteria applied left to right
+     */
     public void sort(SortCriteria... sortCriterias) {
         rows.sort(new Comparator() {
             @Override
@@ -152,6 +201,9 @@ public String toString() {
         return sb.toString();
     }
 
+    /**
+     * Placeholder value used for missing cells when sorting or exporting.
+     */
     public static class EmptyCell implements Comparable {
         @Override
         public int compareTo(Object o) {
@@ -167,6 +219,12 @@ public String toString() {
         }
     }
 
+    /**
+     * Serializes the table to CSV text.
+     *
+     * @param headers optional CSV header row
+     * @return CSV representation of the table
+     */
     public String toCSV(String... headers) {
         StringBuilder stringBuilder = new StringBuilder();
         CSVFormat csvFormat = CSVFormat.DEFAULT;
diff --git a/config/checkstyle-javadoc.xml b/config/checkstyle-javadoc.xml
new file mode 100644
index 000000000..1c5f09ced
--- /dev/null
+++ b/config/checkstyle-javadoc.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+    
+    
+        
+            
+            
+        
+        
+            
+        
+        
+            
+            
+        
+        
+            
+        
+    
+
diff --git a/docker/README.md b/docker/README.md
index 8cfa6c048..c5ab6553d 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -83,7 +83,7 @@ docker pull apache/unomi:3.1.0-SNAPSHOT
 docker run -d --name unomi --net unomi -p 8181:8181 -p 9443:9443 -p 8102:8102 \
     -e UNOMI_DISTRIBUTION=unomi-distribution-opensearch \
     -e UNOMI_OPENSEARCH_ADDRESSES=opensearch:9200 \
-    -e UNOMI_OPENSEARCH_PASSWORD=${OPENSEARCH_ADMIN_PASSWORD} 
+    -e UNOMI_OPENSEARCH_PASSWORD=${OPENSEARCH_ADMIN_PASSWORD} \
     apache/unomi:3.1.0-SNAPSHOT
 ```
 
@@ -101,7 +101,7 @@ For OpenSearch:
 
 ```bash
 docker run -d --name unomi -p 8181:8181 -p 9443:9443 -p 8102:8102 \
-    -e UNOMI_DISTRIBUTION=unomi-distribution-opensearch
+    -e UNOMI_DISTRIBUTION=unomi-distribution-opensearch \
     -e UNOMI_OPENSEARCH_ADDRESSES=host.docker.internal:9200 \
     -e UNOMI_OPENSEARCH_PASSWORD=${OPENSEARCH_ADMIN_PASSWORD} \
     apache/unomi:3.1.0-SNAPSHOT
@@ -125,6 +125,23 @@ Note: Linux doesn't support the host.docker.internal DNS lookup method yet, it s
 - `UNOMI_OPENSEARCH_ADDRESSES`: OpenSearch host:port (default: localhost:9200)
 - `UNOMI_OPENSEARCH_PASSWORD`: Required admin password for OpenSearch (SSL and authentication are mandatory)
 
+
+
+## First steps after startup (Unomi 3.1+)
+
+Multi-tenancy requires a tenant before client endpoints such as `/cxs/context.json` accept traffic:
+
+```bash
+curl -X POST http://localhost:8181/cxs/tenants \
+  --user karaf:karaf \
+  -H "Content-Type: application/json" \
+  -d '{"requestedId":"default","properties":{"name":"Default Tenant"}}'
+```
+
+Save the public and private API keys from the response. Use the public key in the `X-Unomi-Api-Key` header for public endpoints.
+
+See the [Multi-tenancy](https://github.com/apache/unomi/blob/master/manual/src/main/asciidoc/multitenancy.adoc) chapter in the manual for details.
+
 # Using docker build tools
 
 If you want to rebuild the images or use docker compose directly, you must still first use `mvn clean install` to generate
diff --git a/extensions/healthcheck/README.md b/extensions/healthcheck/README.md
index 323768e0e..fccbf65d7 100644
--- a/extensions/healthcheck/README.md
+++ b/extensions/healthcheck/README.md
@@ -36,8 +36,8 @@ The healthcheck is available even if unomi is not started. It gives health infor
 
 All healthcheck can have a status :
   - DOWN (service is not available)
-  - UP (service is up but does not respond to request (starting or misconfigured))
-  - LIVE (service is ready to serve request)
+  - UP (service is running or starting)
+  - LIVE (service is ready to serve requests)
   - ERROR (an error occurred during service health check)
 
 Any subsystem health check have a timeout of 500ms where check is cancelled and will be returned as error.
diff --git a/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/ImportConfiguration.java b/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/ImportConfiguration.java
index fd047566e..02cef325e 100644
--- a/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/ImportConfiguration.java
+++ b/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/ImportConfiguration.java
@@ -23,12 +23,15 @@
 import java.util.*;
 
 /**
- * Created by amidani on 28/04/2017.
+ * Router-side import job definition for bulk loading Unomi items.
+ * Extends {@link ImportExportConfiguration} with import-specific column mappings,
+ * type mappings, and merge behavior. Camel routes in the router extension read
+ * this configuration to stream external files into the persistence layer.
  */
 public class ImportConfiguration extends ImportExportConfiguration {
 
     /**
-     * The ImportConfiguration ITEM_TYPE
+     * Persistence item type for router import configurations.
      *
      * @see Item for a discussion of ITEM_TYPE
      */
@@ -39,81 +42,91 @@ public class ImportConfiguration extends ImportExportConfiguration {
     private boolean hasHeader = false;
     private boolean hasDeleteColumn = false;
 
-
+    /**
+     * Returns the property used to match existing profiles during import.
+     *
+     * @return the merging property name
+     */
     public String getMergingProperty() {
         return mergingProperty;
     }
 
     /**
-     * Sets the merging property.
-     * @param mergingProperty property used to check if the profile exist when merging
+     * Sets the property used to match existing profiles during import.
+     *
+     * @param mergingProperty property used to check whether the profile exists when merging
      */
     public void setMergingProperty(String mergingProperty) {
         this.mergingProperty = mergingProperty;
     }
 
     /**
-     * Retrieves the import configuration overwriteExistingProfiles flag.
+     * Returns whether existing profiles are overwritten during import.
      *
-     * @return true if during the import existing profiles must be overwritten
+     * @return {@code true} when existing profiles must be overwritten
      */
     public boolean isOverwriteExistingProfiles() {
         return this.overwriteExistingProfiles;
     }
 
     /**
-     * Sets the overwriteExistingProfiles flag true/false.
+     * Sets whether existing profiles are overwritten during import.
      *
-     * @param overwriteExistingProfiles a boolean to set overwriteExistingProfiles in the import configuration
+     * @param overwriteExistingProfiles {@code true} to overwrite existing profiles
      */
     public void setOverwriteExistingProfiles(boolean overwriteExistingProfiles) {
         this.overwriteExistingProfiles = overwriteExistingProfiles;
     }
 
     /**
-     * Retrieves the import configuration propertiesToOverwrite field.
+     * Returns profile property names that may be overwritten during import.
      *
-     * @return propertiesToOverwrite list.
+     * @return the property names to overwrite, or {@code null} when not restricted
      */
     public List getPropertiesToOverwrite() {
         return propertiesToOverwrite;
     }
 
+    /**
+     * Sets the list of profile properties that may be overwritten during import.
+     *
+     * @param propertiesToOverwrite property names to overwrite
+     */
     public void setPropertiesToOverwrite(List propertiesToOverwrite) {
         this.propertiesToOverwrite = propertiesToOverwrite;
     }
 
     /**
-     * Retrieves the hasHeader flag.
+     * Returns whether the import file includes a header row.
      *
-     * @return true if the file imported by the current config has a header line.
+     * @return {@code true} when the first row is a header
      */
     public boolean isHasHeader() {
         return this.hasHeader;
     }
 
     /**
-     * Sets the hasHeader flag.
+     * Sets whether the import file includes a header row.
      *
-     * @param hasHeader new value for hasHeader
+     * @param hasHeader {@code true} when the first row is a header
      */
     public void setHasHeader(boolean hasHeader) {
         this.hasHeader = hasHeader;
     }
 
     /**
-     * Retrieves the hasDeleteColumn flag.
+     * Returns whether the import file includes a delete column.
      *
-     * @return true if the file imported by the current config has a delete column.
+     * @return {@code true} when a delete column is present
      */
     public boolean isHasDeleteColumn() {
         return this.hasDeleteColumn;
     }
 
     /**
-     * Sets the hasDeleteColumn flag.
+     * Sets whether the import file includes a delete column.
      *
-     * @param hasDeleteColumn new value for hasDeleteColumn
+     * @param hasDeleteColumn {@code true} when a delete column is present
      */
     public void setHasDeleteColumn(boolean hasDeleteColumn) {
         this.hasDeleteColumn = hasDeleteColumn;
diff --git a/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/RouterConstants.java b/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/RouterConstants.java
index 5576296d8..373b8e41f 100644
--- a/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/RouterConstants.java
+++ b/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/RouterConstants.java
@@ -17,9 +17,14 @@
 package org.apache.unomi.router.api;
 
 /**
- * Created by amidani on 13/06/2017.
+ * Shared constant names for the Unomi router import/export Camel integration.
+ * Defines configuration types, route identifiers, and event names used when
+ * import or export jobs are registered, refreshed, or torn down.
  */
 public interface RouterConstants {
+    /**
+     * Camel configuration refresh event types.
+     */
     enum CONFIG_CAMEL_REFRESH {
         UPDATED,
         REMOVED
diff --git a/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/RouterUtils.java b/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/RouterUtils.java
index a535206c9..7109b468d 100644
--- a/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/RouterUtils.java
+++ b/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/RouterUtils.java
@@ -23,10 +23,20 @@
 import java.util.Map;
 
 /**
- * Created by amidani on 30/06/2017.
+ * Helper routines for building router import/export column mappings.
+ * Converts Unomi {@link PropertyType} metadata into header lists and default
+ * values used when CSV or similar files are parsed by router Camel processors.
  */
 public class RouterUtils {
 
+    /**
+     * Appends an execution entry to the configuration history, trimming oldest entries when the limit is reached.
+     *
+     * @param configuration the import/export configuration to update
+     * @param execution the execution metadata to record
+     * @param executionsHistorySize maximum number of execution entries to retain
+     * @return the updated configuration
+     */
     public static ImportExportConfiguration addExecutionEntry(ImportExportConfiguration configuration, Map execution, int executionsHistorySize) {
         if (configuration.getExecutions() == null) {
             configuration.setExecutions(new ArrayList<>());
@@ -47,6 +57,12 @@ public static ImportExportConfiguration addExecutionEntry(ImportExportConfigurat
         return configuration;
     }
 
+    /**
+     * Converts a line-separator string to its single-character form.
+     *
+     * @param lineSeparator the configured line separator (for example {@code "\n"} or {@code "\r"})
+     * @return the corresponding line-separator character
+     */
     public static char getCharFromLineSeparator(String lineSeparator) {
         char charLineSep = '\n';
         if ("\r".equals(lineSeparator)) {
@@ -55,6 +71,13 @@ public static char getCharFromLineSeparator(String lineSeparator) {
         return charLineSep;
     }
 
+    /**
+     * Finds a property type by identifier in a collection.
+     *
+     * @param propertyTypes the property types to search
+     * @param propertyTypeId the property type identifier to match
+     * @return the matching property type, or {@code null} if none is found
+     */
     public static PropertyType getPropertyTypeById(Collection propertyTypes, String propertyTypeId) {
         for (PropertyType propertyType : propertyTypes) {
             if (propertyType.getMetadata().getId().equals(propertyTypeId)) {
diff --git a/graphql/README.md b/graphql/README.md
index ee39d64b3..80d78aefe 100644
--- a/graphql/README.md
+++ b/graphql/README.md
@@ -18,6 +18,15 @@
 Apache Unomi GraphQL API
 ========================
 
+
+## Authentication (Unomi 3.1+)
+
+- **Public `cdp` queries**: `X-Unomi-Api-Key` header with a tenant public API key
+- **Mutations / admin queries**: Basic auth `tenantId:privateApiKey` or JAAS + `X-Unomi-Tenant-Id`
+- **Introspection**: allowed without credentials
+
+Enable GraphQL via `unomi-distribution-*-graphql` or the `cdp-graphql-feature` Karaf feature. See `manual/src/main/asciidoc/graphql.adoc`.
+
 Install
 -------
 
diff --git a/manual/pom.xml b/manual/pom.xml
index aa1b41eaf..82c7b4858 100644
--- a/manual/pom.xml
+++ b/manual/pom.xml
@@ -84,8 +84,11 @@
                             
                                 
                                 true
-                                apache.css
+                                unomi-manual.css
                                 images
+                                highlightjs
+                                ${project.version}
+                                ${project.version}
                             
                         
                     
@@ -107,15 +110,17 @@
                             pdf
                             
                                 ${project.version}
+                                ${project.version}
                                 ${project.basedir}/src/theme
-                                apache
+                                unomi
                                 ${project.basedir}/src/theme/fonts
                                 images
+                                ${project.build.directory}/generated-images
                                 font
                                 true
                                 
                                 
-                                -
+                                _
                                 true
                             
                         
diff --git a/manual/src/main/asciidoc/5-min-quickstart.adoc b/manual/src/main/asciidoc/5-min-quickstart.adoc
index 420095493..e1aacb07d 100644
--- a/manual/src/main/asciidoc/5-min-quickstart.adoc
+++ b/manual/src/main/asciidoc/5-min-quickstart.adoc
@@ -12,7 +12,7 @@
 // limitations under the License.
 //
 
-[[_five_minutes_quickstart]]
+[#_five_minutes_quickstart]
 === Quick start with Docker
 
 Begin by creating a `docker-compose.yml` file. You can choose between ElasticSearch or OpenSearch:
@@ -24,7 +24,7 @@ Begin by creating a `docker-compose.yml` file. You can choose between ElasticSea
 version: '3.8'
 services:
     elasticsearch:
-        image: docker.elastic.co/elasticsearch/elasticsearch:9.2.1
+        image: docker.elastic.co/elasticsearch/elasticsearch:9.4.3
         environment:
             - discovery.type=single-node
         ports:
@@ -94,6 +94,26 @@ volumes:
 
 From the same folder, start the environment using `docker-compose up` and wait for the startup to complete.
 
+==== After startup (ElasticSearch Docker path)
+
+Once Unomi is running, create a tenant and save the API keys from the response:
+
+[source,bash]
+----
+curl -X POST http://localhost:8181/cxs/tenants \
+  --user karaf:karaf \
+  -H "Content-Type: application/json" \
+  -d '{
+    "requestedId": "default",
+    "properties": {
+      "name": "Default Tenant",
+      "description": "Default tenant for quick start"
+    }
+  }'
+----
+
+Use the public API key in `X-Unomi-Api-Key` for `/cxs/context.json` requests. See <<_multitenancy,Multi-tenancy>>.
+
 Try accessing https://localhost:9443/cxs/cluster with username/password: karaf/karaf . You might get a certificate warning in your browser, just accept it despite the warning it is safe.
 
 === Quick Start manually
@@ -102,7 +122,7 @@ Try accessing https://localhost:9443/cxs/cluster with username/password: karaf/k
 
 1) Install JDK 17 and make sure you set the JAVA_HOME variable (see our <<_jdk_compatibility,Getting Started>> guide for more information on JDK compatibility)
 
-2) Download ElasticSearch here : https://www.elastic.co/downloads/past-releases/elasticsearch-9-2-1 (please *make sure* you use the proper version : 9.2.1)
+2) Download ElasticSearch here : https://www.elastic.co/downloads/past-releases/elasticsearch-9-4-3 (please *make sure* you use the proper version : 9.4.3)
 
 3) Uncompress it and change the `config/elasticsearch.yml` to include the following config :
 
@@ -115,7 +135,7 @@ cluster.name: contextElasticSearch
 
 ==== Option 2: Using OpenSearch
 
-1) Install JDK 11 as described above
+1) Install JDK 17 as described above
 
 2) Download OpenSearch here: https://opensearch.org/downloads.html (please *make sure* you use version 3.x)
 
diff --git a/manual/src/main/asciidoc/architecture.adoc b/manual/src/main/asciidoc/architecture.adoc
index 0bfaa1376..383809d11 100644
--- a/manual/src/main/asciidoc/architecture.adoc
+++ b/manual/src/main/asciidoc/architecture.adoc
@@ -14,6 +14,8 @@
 
 === High-Level Architecture
 
+NOTE: Unomi 3.1 adds <<_multitenancy,multi-tenancy>>, <<_scheduler,task scheduling>>, and <<_clustering,persistence-based clustering>>. The diagram below focuses on core request processing.
+
 [plantuml]
 ----
 @startuml
diff --git a/manual/src/main/asciidoc/building-and-deploying.adoc b/manual/src/main/asciidoc/building-and-deploying.adoc
index a5a0e0c1f..ad9987d17 100644
--- a/manual/src/main/asciidoc/building-and-deploying.adoc
+++ b/manual/src/main/asciidoc/building-and-deploying.adoc
@@ -12,7 +12,7 @@
 // limitations under the License.
 //
 
-[[_building]]
+[#_building]
 === Building
 
 Apache Unomi 3.x provides a convenient `build.sh` script that simplifies the build process with
@@ -72,7 +72,7 @@ TIP: On a non-English Windows env, the Asciidoctor Maven Plugin may fail to
 +
 . The distributions will be available under "package/target" directory.
 
-[[_using_the_build_script]]
+[#_using_the_build_script]
 ==== Using the build.sh script
 
 Apache Unomi 3.x provides a unified `build.sh` script that simplifies the build, deployment, and execution process.
@@ -240,11 +240,11 @@ To merge the branch into master.
 
 ==== Installing a Search Engine
 
-Starting with version 1.2, Apache Unomi no longer embeds a search engine server. You will need to install either ElasticSearch or OpenSearch as a standalone service.
+Apache Unomi 3.x does not embed a search engine. Install Elasticsearch 9.x or OpenSearch 3.x as a standalone service. See <<_migrate_from_2_x_to_3_0,Migrate from 2.x to 3.0>> for version requirements.
 
 ===== Option 1: Using ElasticSearch
 
-1. Download ElasticSearch 7.17.5 from: https://www.elastic.co/downloads/past-releases/elasticsearch-7-17-5[https://www.elastic.co/downloads/past-releases/elasticsearch-7-17-5]
+1. Download Elasticsearch 9.4.3 from: https://www.elastic.co/downloads/past-releases/elasticsearch-9-4-3[https://www.elastic.co/downloads/past-releases/elasticsearch-9-4-3]
 
 2. Uncompress the downloaded package into a directory
 
@@ -367,7 +367,7 @@ This is only needed if you didn't use the generated package. Also, this is the p
 environment if you intend to re-deploy the context server KAR iteratively.
 
 Additional requirements:
-* Apache Karaf 4.2.x, http://karaf.apache.org[http://karaf.apache.org]
+* Apache Karaf 4.4.x (Unomi 3.1 ships with 4.4.11), Java 17+ — http://karaf.apache.org[http://karaf.apache.org]
 
 Before deploying, make sure that you have Apache Karaf properly installed. Depending of your usage, you may also have to increase the
  memory size by adjusting the following environment values in the bin/setenv(.bat)
@@ -384,7 +384,7 @@ Install the WAR support and CXF into Karaf by doing the following in the Karaf c
 
 [source]
 ----
-   feature:repo-add cxf-jaxrs 3.3.4
+   feature:repo-add cxf-jaxrs 3.6.x (match the CXF version pinned in the Unomi Karaf features)
    feature:repo-add mvn:org.apache.unomi/unomi-kar/VERSION/xml/features
    feature:install unomi-kar
 ----
diff --git a/manual/src/main/asciidoc/builtin-action-types.adoc b/manual/src/main/asciidoc/builtin-action-types.adoc
index 7163fc6da..5fd36ea80 100644
--- a/manual/src/main/asciidoc/builtin-action-types.adoc
+++ b/manual/src/main/asciidoc/builtin-action-types.adoc
@@ -11,60 +11,96 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 //
-[[_builtin_action_types]]
+
+[#_builtin_action_types]
 === Built-in action types
 
-Unomi comes with quite a lot of built-in action types. Instead of detailing them one by one you will find here an overview of
-what an action type descriptor looks like:
+Actions run when a rule matches. Each action type references an `actionExecutor` OSGi service.
+
+==== Action type descriptor shape
 
 [source,json]
 ----
 {
   "metadata": {
-    "id": "UNIQUE_IDENTIFIER_STRING",
-    "name": "DISPLAYABLE_ACTION_NAME",
-    "description": "DISPLAYABLE_ACTION_DESCRIPTION",
-    "systemTags": [
-      "profileTags",
-      "event",
-      "availableToEndUser",
-      "allowMultipleInstances"
-    ],
+    "id": "setPropertyAction",
+    "name": "setPropertyAction",
+    "systemTags": ["profileTags", "availableToEndUser"],
     "readOnly": true
   },
-  "actionExecutor": "ACTION_EXECUTOR_ID",
+  "actionExecutor": "setProperty",
   "parameters": [
-     ... parameters specific to each action ...
+    { "id": "setPropertyName", "type": "string" },
+    { "id": "setPropertyValue", "type": "string" }
   ]
 }
 ----
 
-The ACTION_EXECUTOR_ID points to a OSGi Blueprint parameter that is defined when implementing the action in a plugin.
-Here's an example of such a registration:
-
-From https://github.com/apache/unomi/blob/master/plugins/mail/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+==== OSGi registration example
 
 [source,xml]
 ----
-    
-       
-    
-    
-        
-            
-        
-    
+
+
+    
+        
+    
+
 ----
 
-In the above example the ACTION_EXECUTOR_ID is `sendMail`
+==== Catalog
+
+[cols="2,2,2,4", options="header"]
+|===
+| Action type ID | Source | Executor | Parameters / description
+
+| `addToListsAction` | lists-extension | `addToLists` | Params: listIdentifiers
+| `allEventToProfilePropertiesAction` | baseplugin | `allEventToProfileProperties` | Params: —
+| `cdpSessionEventAction` | graphql | `cdp_sessionEvent` | Params: state, sessionId, scope
+| `copyPropertiesAction` | baseplugin | `copyProperties` | Params: setPropertyStrategy, rootProperty, mandatoryPropTypeSystemTag
+| `evaluateProfileAgeAction` | baseplugin | `evaluateProfileAge` | Params: —
+| `evaluateProfileSegmentsAction` | baseplugin | `evaluateProfileSegments` | Params: —
+| `evaluateVisitPropertiesAction` | baseplugin | `evaluateVisitProperties` | Params: —
+| `eventToProfilePropertyAction` | baseplugin | `eventToProfileProperty` | Params: eventPropertyName, profilePropertyName
+| `incrementPropertyAction` | baseplugin | `incrementProperty` | Params: propertyName, propertyTarget, storeInSession
+| `mergeProfilesOnPropertyAction` | baseplugin | `mergeProfilesOnProperty` | Params: mergeProfilePropertyName, forceEventProfileAsMaster
+| `modifyConsentAction` | baseplugin | `modifyConsent` | Modify a profile consent. Params: —
+| `requestHeaderToProfilePropertyAction` | request | `requestHeaderToProfileProperty` | Params: requestHeaderName, profilePropertyName, sessionPropertyName, storeAsList
+| `requestParameterToProfilePropertyAction` | request | `requestParameterToProfileProperty` | Params: requestParameterName, profilePropertyName, sessionPropertyName, storeAsList
+| `sendEventAction` | baseplugin | `sendEvent` | Params: eventType, eventProperties
+| `sendMailAction` | mail | `sendMail` | Params: notificationType, notificationTypeId, notifyOncePerProfile, from, to, cc, bcc, subject, template
+| `setEventOccurenceCountAction` | baseplugin | `setEventOccurenceCount` | Params: pastEventCondition
+| `setPropertyAction` | baseplugin | `setProperty` | Params: setPropertyName, setPropertyValue, setPropertyValueBoolean, setPropertyValueInteger, setPropertyValueMultiple, setPropertyValueCurrentEventTimestamp, setPropertyValueCurrentDate, setPropertyStrategy, storeInSession
+| `setRemoteHostInfoAction` | request | `setRemoteHostInfo` | Params: —
+| `sfdcCreateOrUpdateLeadAction` | salesforce-connector | `sfdcCreateOrUpdateLead` | Params: —
+| `sfdcUpdateProfileFromLeadAction` | salesforce-connector | `sfdcUpdateProfileFromLead` | Params: —
+| `updateConsentEventAction` | graphql | `updateConsent` | Params: type, status, lastUpdate, expiration
+| `updateListsEventAction` | graphql | `updateLists` | Params: join_lists, leave_lists
+| `updatePropertiesAction` | baseplugin | `updateProperties` | Update multiple properties in profile/persona. Params: —
+| `weatherUpdateAction` | weather-update | `weatherUpdate` | This action will retrieve the weather associated with the resolved location of the user (from his IP address). Params: —
+|===
+
+==== Common action groups
+
+*Profile maintenance* — `setPropertyAction`, `updatePropertiesAction`, `incrementPropertyAction`, `copyPropertiesAction`, `eventToProfilePropertyAction`, `allEventToProfilePropertiesAction`, `mergeProfilesOnPropertyAction`
+
+*Segmentation & scoring* — `evaluateProfileSegmentsAction`, `evaluateProfileAgeAction`, `evaluateVisitPropertiesAction`
+
+*Events & consent* — `sendEventAction`, `modifyConsentAction`, `setEventOccurenceCountAction`
+
+*HTTP request* — `requestHeaderToProfilePropertyAction`, `requestParameterToProfilePropertyAction`, `setRemoteHostInfoAction` (request plugin)
+
+*Notifications* — `sendMailAction` (mail plugin)
+
+*Integrations* — `addToListsAction`, Salesforce actions, `weatherUpdateAction`, GraphQL CDP actions (when GraphQL feature is installed)
 
-==== Existing action types descriptors
+==== Source locations
 
-Here is a non-exhaustive list of actions built into Apache Unomi. Feel free to browse the source code if you want to
-discover more. But the list below should get you started with the most useful actions:
+* `plugins/baseplugin/src/main/resources/META-INF/cxs/actions/`
+* `plugins/mail/src/main/resources/META-INF/cxs/actions/`
+* `plugins/request/src/main/resources/META-INF/cxs/actions/`
+* Extension plugins under `extensions/*/src/main/resources/META-INF/cxs/actions/`
 
-- https://github.com/apache/unomi/tree/master/plugins/baseplugin/src/main/resources/META-INF/cxs/actions
-- https://github.com/apache/unomi/tree/master/plugins/request/src/main/resources/META-INF/cxs/actions
-- https://github.com/apache/unomi/tree/master/plugins/mail/src/main/resources/META-INF/cxs/actions
+List action types at runtime: `GET /cxs/definitions/actions` (tenant private key or admin).
 
-Of course it is also possible to build your own custom actions by developing custom Unomi plugins/extensions.
\ No newline at end of file
+See <<_writing_plugins,Writing plugins>> to implement custom actions.
diff --git a/manual/src/main/asciidoc/builtin-condition-types.adoc b/manual/src/main/asciidoc/builtin-condition-types.adoc
index 247d5252c..b4e56a321 100644
--- a/manual/src/main/asciidoc/builtin-condition-types.adoc
+++ b/manual/src/main/asciidoc/builtin-condition-types.adoc
@@ -12,11 +12,14 @@
 // limitations under the License.
 //
 
-[[_builtin_condition_types]]
+[#_builtin_condition_types]
 === Built-in condition types
 
-Apache Unomi comes with an extensive collection of built-in condition types. Instead of detailing them one by one you will
-find here an overview of what a JSON condition descriptor looks like:
+Apache Unomi ships with built-in condition types in the base plugin and optional extensions.
+For a hands-on introduction with examples, see <<_conditions_guide,Conditions guide>>.
+For save-time parameter rules (`required`, `allowedValues`, exclusive groups), see <<_condition_validation,Condition validation>>.
+
+==== Condition type descriptor shape
 
 [source,json]
 ----
@@ -24,16 +27,8 @@ find here an overview of what a JSON condition descriptor looks like:
   "metadata": {
     "id": "booleanCondition",
     "name": "booleanCondition",
-    "description": "",
-    "systemTags": [
-      "profileTags",
-      "logical",
-      "condition",
-      "profileCondition",
-      "eventCondition",
-      "sessionCondition",
-      "sourceEventCondition"
-    ],
+    "description": "Combines multiple conditions with a logical operator",
+    "systemTags": ["logical", "profileCondition", "eventCondition"],
     "readOnly": true
   },
   "conditionEvaluator": "booleanConditionEvaluator",
@@ -41,71 +36,123 @@ find here an overview of what a JSON condition descriptor looks like:
   "parameters": [
     {
       "id": "operator",
-      "type": "String",
-      "multivalued": false,
-      "defaultValue": "and"
+      "type": "string",
+      "defaultValue": "and",
+      "validation": { "required": true, "allowedValues": ["and", "or"] }
     },
     {
       "id": "subConditions",
       "type": "Condition",
-      "multivalued": true
+      "multivalued": true,
+      "validation": { "required": true }
     }
   ]
 }
 ----
 
-Note that condition types have three important identifiers:
+Parameters may include a `validation` object (see <<_condition_validation,Condition validation>>).
+Real descriptors in the base plugin use this heavily — for example `pastEventCondition` requires an `eventCondition` sub-condition tagged `eventCondition`.
 
-- conditionEvaluator: For real-time condition evaluation
-- queryBuilder: For building search engine queries (either ElasticSearch or OpenSearch)
+Each condition type provides:
 
-This is because condition types can be used in three ways:
-1. To evaluate a condition in real time
-2. To build ElasticSearch queries
-3. To build OpenSearch queries
+* `conditionEvaluator` — real-time evaluation against profile, session, and/or event
+* `queryBuilder` — converts the condition to an Elasticsearch or OpenSearch query (Unomi 3.0+ uses generic `*QueryBuilder` IDs)
 
-When implementing a new condition type, you need to provide implementations for each use case. Here's an example of OSGi Blueprint registrations for the above condition type descriptor:
+Some types use `parentCondition` to specialize a base type (for example `eventTypeCondition` extends `eventPropertyCondition`).
 
-[source,xml]
-----
-
-
-    
-        
-    
-    
-
-
-
-
-    
-        
-    
-    
-
-
-
-
-    
-        
-    
-    
-
-----
+==== Plugin registration (Unomi 3.0+)
 
-As you can see, three Java classes are used to build a single condition type:
-1. A condition evaluator (shared)
-2. An ElasticSearch query builder
-3. An OpenSearch query builder
+New plugins use **Declarative Services** (`@Component`). Legacy Blueprint XML remains in some shipped bundles during migration; do not copy it for new code. In particular, the built-in `BooleanConditionEvaluator`, `BooleanConditionESQueryBuilder`, and `BooleanConditionOSQueryBuilder` classes are themselves still registered via Blueprint XML today — the snippets below illustrate how a **new** condition type should be registered, not the current state of those specific built-in classes.
 
-You don't need to understand all these details to use condition types, but this might be interesting if you're building your own condition type implementations. For more details on building custom plugins/extensions with search engine specific implementations, please refer to the corresponding sections.
+Condition evaluator (see `plugins/advanced-conditions`):
 
-==== Existing condition type descriptors
+[source,java]
+----
+@Component(service = ConditionEvaluator.class, property = {"conditionEvaluatorId=myCustomConditionEvaluator"})
+public class MyCustomConditionEvaluator implements ConditionEvaluator {
+    // ...
+}
+----
 
-Here is a non-exhaustive list of condition types built into Apache Unomi. Feel free to browse the source code if you want to discover more. The list below should get you started with the most useful conditions:
+Elasticsearch query builder (in your ES-specific bundle):
 
-- https://github.com/apache/unomi/tree/master/plugins/baseplugin/src/main/resources/META-INF/cxs/conditions
+[source,java]
+----
+@Component(service = ConditionESQueryBuilder.class, property = {"queryBuilderId=myCustomConditionQueryBuilder"})
+public class MyCustomConditionESQueryBuilder implements ConditionESQueryBuilder {
+    // ...
+}
+----
+
+OpenSearch query builder (in your OS-specific bundle):
 
-Of course it is also possible to build your own custom condition types by developing custom Unomi plugins/extensions.
+[source,java]
+----
+@Component(service = ConditionOSQueryBuilder.class, property = {"queryBuilderId=myCustomConditionQueryBuilder"})
+public class MyCustomConditionOSQueryBuilder implements ConditionOSQueryBuilder {
+    // ...
+}
+----
 
-You will also note that some condition types can re-use a `parentCondition`. This is a way to inherit from another condition type to make them more specific.
+Use the same `queryBuilderId` in JSON, Elasticsearch, and OpenSearch registrations. See <<_writing_plugins,Writing plugins>> for module layout and POM setup.
+
+==== Catalog — base plugin
+
+[cols="2,3,2,4", options="header"]
+|===
+| Condition type ID | Category | Parent | Key parameters / notes
+
+| `booleanCondition` | Event | — | Combines multiple conditions with a logical operator. Params: operator, subConditions
+| `eventPropertyCondition` | Event | — | Params: propertyName, comparisonOperator, propertyValue, propertyValueInteger, propertyValueDouble, propertyValueDate, propertyValueDateExpr, propertyValues
+| `eventTypeCondition` | Event | eventPropertyCondition | Params: eventTypeId
+| `formEventCondition` | Event | booleanCondition | Params: formId, pagePath
+| `geoLocationByPointSessionCondition` | Session | — | Params: type, rectLatitudeNE, rectLongitudeNE, rectLatitudeSW, rectLongitudeSW, circleLatitude, circleLongitude, distance
+| `geoLocationSessionCondition` | Session | booleanCondition | Params: country, admin1, admin2, city
+| `goalMatchCondition` | Profile | profilePropertyCondition | Params: goalId, comparisonOperator
+| `idsCondition` | Other | — | Params: ids, match
+| `matchAllCondition` | Event | — | Params: —
+| `modifyAnyConsentEventCondition` | Event | eventTypeCondition | Used to match any consent modification. Params: —
+| `modifyConsentEventCondition` | Event | booleanCondition | Used to match status modifications on a specific consent. Params: consentTypeId, consentStatus
+| `nestedCondition` | Session | — | Evaluates a condition on nested items within a profile or session property. Params: path, subCondition
+| `newVisitorCondition` | Session | booleanCondition | Params: since
+| `notCondition` | Event | — | Params: subCondition
+| `pastEventCondition` | Profile | — | Evaluates past events that occurred within a specified time range. Params: eventCondition, operator, numberOfDays, fromDate, toDate, minimumEventCount, maximumEventCount
+| `profileAliasesPropertyCondition` | Other | — | Params: propertyName, comparisonOperator, propertyValue
+| `profilePropertyCondition` | Profile | — | Evaluates a profile property against various comparison criteria. Params: propertyName, comparisonOperator, propertyValue, propertyValueInteger, propertyValueDouble, propertyValueDate, propertyValueDateExpr, propertyValues
+| `profileSegmentCondition` | Profile | profilePropertyCondition | Params: segments, matchType
+| `profileUpdatedEventCondition` | Event | eventTypeCondition | Params: —
+| `profileUserListCondition` | Profile | profilePropertyCondition | Params: lists, matchType
+| `returningVisitorCondition` | Session | booleanCondition | Params: since
+| `scoringCondition` | Profile | profilePropertyCondition | Params: scoringPlanId, scoreValue, comparisonOperator
+| `sessionCreatedEventCondition` | Event | eventTypeCondition | Params: —
+| `sessionDurationCondition` | Session | sessionPropertyCondition | Params: minimumDuration, maximumDuration
+| `sessionPropertyCondition` | Session | — | Params: propertyName, comparisonOperator, propertyValue, propertyValueInteger, propertyValueDouble, propertyValueDate, propertyValueDateExpr, propertyValues
+| `topicPropertyCondition` | Other | — | Params: propertyName, comparisonOperator, propertyValue
+| `updatePropertiesEventCondition` | Event | eventTypeCondition | Params: —
+| `videoViewEventCondition` | Event | booleanCondition | Params: videoId, pagePath
+|===
+
+==== Catalog — extensions
+
+[cols="2,2,4", options="header"]
+|===
+| Condition type ID | Source | Key parameters / notes
+| `cdpSessionEventCondition` | graphql | Params: —
+| `hoverEventCondition` | hover-event | Params: targetId, targetPath
+| `sourceEventPropertyCondition` | advanced-conditions | Params: id, path, scope, type
+| `updateConsentEventCondition` | graphql | Params: —
+| `updateListsEventCondition` | graphql | Params: —
+| `userListPropertyCondition` | graphql | Params: propertyName, comparisonOperator, propertyValue, propertyValues
+|===
+
+==== Source locations
+
+Condition descriptors are JSON files under `META-INF/cxs/conditions/` in each plugin, for example:
+
+* `plugins/baseplugin/src/main/resources/META-INF/cxs/conditions/`
+* `plugins/advanced-conditions/src/main/resources/META-INF/cxs/conditions/`
+* `plugins/hover-event/src/main/resources/META-INF/cxs/conditions/`
+
+GraphQL-specific condition types ship with the GraphQL feature (`graphql/cxs-impl/...`).
+
+Custom condition types can be added in your own plugins; see <<_writing_plugins,Writing plugins>>.
diff --git a/manual/src/main/asciidoc/builtin-event-types.adoc b/manual/src/main/asciidoc/builtin-event-types.adoc
index 74ddd7122..800c1b3fe 100644
--- a/manual/src/main/asciidoc/builtin-event-types.adoc
+++ b/manual/src/main/asciidoc/builtin-event-types.adoc
@@ -11,7 +11,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 //
-[[_builtin_event_types]]
+[#_builtin_event_types]
 === Built-in Event types
 
 Apache Unomi comes with built-in event types, which we describe below.
diff --git a/manual/src/main/asciidoc/clustering.adoc b/manual/src/main/asciidoc/clustering.adoc
index 9d31aa6a2..6ffd776e4 100644
--- a/manual/src/main/asciidoc/clustering.adoc
+++ b/manual/src/main/asciidoc/clustering.adoc
@@ -11,19 +11,84 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 //
+[#_clustering]
 === Cluster setup
 
-Apache Karaf relies on Persistence to register nodes and manage cluster.
+Apache Unomi runs as one or more Karaf nodes that share the same Elasticsearch or OpenSearch backend.
+Since Unomi 3.0, cluster coordination uses the **persistence layer** (UNOMI-877): each node registers a
+`ClusterNode` record, publishes periodic heartbeats, and reads peer state from the search index.
+Karaf Cellar / Hazelcast are no longer required.
 
-You can control most of the important clustering settings through the centralized configuration file at
+This design also supports the <<_scheduler,task scheduler>> (cluster-wide locks, executor nodes) and the
+health-check **cluster** provider.
 
-    etc/unomi.custom.system.properties
+==== Required settings
 
-And notably using the following properties:
+Configure each node in `etc/unomi.custom.system.properties` (or via environment variables):
 
-    org.apache.unomi.cluster.public.address=${env:UNOMI_CLUSTER_PUBLIC_ADDRESS:-http://localhost:8181}
-    org.apache.unomi.cluster.internal.address=${env:UNOMI_CLUSTER_INTERNAL_ADDRESS:-https://localhost:9443}
-    org.apache.unomi.cluster.nodeId=${env:UNOMI_CLUSTER_NODEID:-unomi-node-1}
-    org.apache.unomi.cluster.nodeStatisticsUpdateFrequency=${env:UNOMI_CLUSTER_NODESTATISTICS_UPDATEFREQUENCY:-10000}
+[source,properties]
+----
+org.apache.unomi.cluster.public.address=${env:UNOMI_CLUSTER_PUBLIC_ADDRESS:-http://localhost:8181}
+org.apache.unomi.cluster.internal.address=${env:UNOMI_CLUSTER_INTERNAL_ADDRESS:-https://localhost:9443}
+org.apache.unomi.cluster.nodeId=${env:UNOMI_CLUSTER_NODEID:-unomi-node-1}
+org.apache.unomi.cluster.nodeStatisticsUpdateFrequency=${env:UNOMI_CLUSTER_NODESTATISTICS_UPDATEFREQUENCY:-10000}
+----
 
-Note that it is mandatory to set a different `org.apache.unomi.cluster.nodeId` for each node in the cluster.
+* **public.address** — URL other nodes and operators use to reach this node (HTTP, port 8181 by default).
+* **internal.address** — HTTPS URL for the private REST API (port 9443 by default).
+* **nodeId** — **Must be unique** on every node in the cluster (for example `unomi-node-1`, `unomi-node-2`).
+* **nodeStatisticsUpdateFrequency** — Interval in milliseconds between heartbeat / statistics updates (default 10 seconds).
+
+If `nodeId` is missing or duplicate, cluster registration fails at startup.
+
+==== How it works
+
+On startup the cluster service:
+
+1. Registers this node in persistence (`ClusterNode` item).
+2. Schedules a recurring task to update CPU, load, and uptime statistics.
+3. Schedules stale-node cleanup (nodes with no heartbeat for roughly three times the update interval are removed).
+
+Other nodes see the same registry through the shared search backend. The health-check extension reports
+cluster size and per-node status when you call `/health/check` (see <<_health_check,Health Check extension>>).
+
+[plantuml]
+----
+@startuml
+skinparam componentStyle rectangle
+
+database "Elasticsearch / OpenSearch" as IDX
+node "Unomi node A\n(nodeId=node-1)" as A
+node "Unomi node B\n(nodeId=node-2)" as B
+
+A --> IDX : register ClusterNode,\nheartbeat stats
+B --> IDX : register ClusterNode,\nheartbeat stats
+A ..> B : optional HTTP\n(public address)
+@enduml
+----
+
+==== Scheduler and executor nodes
+
+Background tasks can be marked **persistent** (stored in the index, visible on all nodes) or **local**
+(single node only). Only nodes that run the task executor process scheduled work.
+
+* Use the Karaf command `unomi:task-executor` to see or toggle executor mode on a node.
+* See <<_scheduler,Task scheduler>> for REST and shell operations on tasks.
+
+In a typical cluster you run multiple Unomi nodes with **distinct `nodeId` values**, all pointing at the
+same search backend, and enable the executor on every node that should run background jobs (or on a subset
+for dedicated worker nodes).
+
+==== Operations
+
+* **Inspect nodes** — `GET /cxs/cluster` (private API, system administrator credentials). Listed in
+  <<_useful_apache_unomi_urls,Useful Apache Unomi URLs>>.
+* **Health** — `GET /health/check` includes a `cluster` provider when the health-check feature is installed.
+* **Stale nodes** — Removed automatically when heartbeats stop; adjust `nodeStatisticsUpdateFrequency` if
+  your network is slow or nodes are under heavy load.
+
+==== Migration from Unomi 2.x / Cellar
+
+If you previously used Karaf Cellar for clustering, plan a full migration to persistence-based clustering
+as part of your Unomi 3 upgrade. See <<_upgrade_jira_reference,JIRA reference>> (UNOMI-877) and
+<<_upgrade_karaf,Karaf upgrade>> for platform constraints.
diff --git a/manual/src/main/asciidoc/condition-evaluation.adoc b/manual/src/main/asciidoc/condition-evaluation.adoc
index 04bc6b661..2171d911c 100644
--- a/manual/src/main/asciidoc/condition-evaluation.adoc
+++ b/manual/src/main/asciidoc/condition-evaluation.adoc
@@ -11,8 +11,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 //
+[#_condition_evaluation]
 === Condition Evaluation System
 
+For examples and progressive tutorials, see <<_conditions_guide,Conditions guide>>.
+
 The condition evaluation system in Apache Unomi provides flexible and efficient evaluation of conditions across different storage backends.
 
 ==== Architecture Overview
@@ -32,8 +35,8 @@ skinparam component {
 package "Condition Evaluation" {
   [ConditionDispatcher] <>
   [ConditionEvaluator] <>
-  [ConditionESQueryBuilder] <>
-  [ConditionOSQueryBuilder] <>
+  [ConditionQueryBuilder] <>
+  [ConditionQueryBuilder] <>
 }
 
 package "Query Building" {
@@ -50,11 +53,11 @@ package "Storage" {
 [ConditionDispatcher] --> [ConditionEvaluator]
 [ConditionDispatcher] --> QB
 
-QB <|.. [ConditionESQueryBuilder]
-QB <|.. [ConditionOSQueryBuilder]
+QB <|.. [ConditionQueryBuilder]
+QB <|.. [ConditionQueryBuilder]
 
-[ConditionESQueryBuilder] --> [ElasticSearchQueryBuilder]
-[ConditionOSQueryBuilder] --> [OpenSearchQueryBuilder]
+[ConditionQueryBuilder] --> [ElasticSearchQueryBuilder]
+[ConditionQueryBuilder] --> [OpenSearchQueryBuilder]
 
 [ElasticSearchQueryBuilder] --> [ElasticSearch]
 [OpenSearchQueryBuilder] --> [OpenSearch]
diff --git a/manual/src/main/asciidoc/condition-validation.adoc b/manual/src/main/asciidoc/condition-validation.adoc
new file mode 100644
index 000000000..fd4aaebcf
--- /dev/null
+++ b/manual/src/main/asciidoc/condition-validation.adoc
@@ -0,0 +1,251 @@
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+[#_condition_validation]
+=== Condition validation
+
+Apache Unomi validates condition instances against their condition type definitions when you save segments, rules, queries, goals, and related items.
+This catches missing parameters, wrong value types, and invalid nested conditions *before* they reach runtime evaluation.
+
+NOTE: This is separate from <<_json_schemas,JSON Schema event validation>>.
+JSON schemas validate incoming events on public endpoints.
+Condition validation checks the structure of condition JSON used in segments, rules, and queries.
+
+See also:
+
+* <<_conditions_guide,Conditions guide>> — hands-on introduction
+* <<_builtin_condition_types,Built-in condition types>> — descriptor format and catalog
+* <<_request_tracing_explain,Request tracing with explain>> — see validation results in trace output
+
+==== When validation runs
+
+Validation runs on the server when a condition is saved or resolved, not on every profile lookup or event.
+
+Typical entry points:
+
+* *Segments* — saving or updating a segment condition (`SegmentServiceImpl`)
+* *Rules* — saving or updating a rule condition (`RulesServiceImpl`)
+* *Queries* — saving queries used in profiles, events, goals, and campaigns
+* *Goals* — start and target event conditions
+* *Condition type resolution* — when `DefinitionsService` resolves a condition tree (deprecated path still used in some flows)
+
+There is *no* standalone REST endpoint such as `/cxs/conditions/validate`.
+To test a condition, save it on a segment or rule and read the error response, or use <<_debugging_conditions,debugging with explain>> when the condition is evaluated at runtime.
+
+==== Validation metadata on condition types
+
+Each parameter in a condition type descriptor can include a `validation` object.
+The base plugin ships examples in `plugins/baseplugin/src/main/resources/META-INF/cxs/conditions/*.json`.
+
+Supported fields:
+
+[cols="1,2,1", options="header"]
+|===
+| Field | Purpose | Example use
+
+| `required`
+| Parameter must have a literal value at save time
+| `propertyName` on `profilePropertyCondition`
+
+| `recommended`
+| Parameter is optional but advised; produces a warning, not a blocking error
+| `minimumEventCount` on `pastEventCondition`
+
+| `allowedValues`
+| String value must be one of the listed values
+| `operator` on `pastEventCondition` (`eventsOccurred`, `eventsNotOccurred`)
+
+| `exclusive` + `exclusiveGroup`
+| Only one parameter in the group may have a value
+| `propertyValue` vs `propertyValueInteger` on `profilePropertyCondition`
+
+| `allowedConditionTags`
+| Nested condition must carry at least one of these system tags
+| `eventCondition` tag required for `eventCondition` on `pastEventCondition`
+
+| `disallowedConditionTypes`
+| Nested condition type ID must not be in this list
+| Restrict which sub-conditions a composite type accepts
+|===
+
+Example from `pastEventCondition`:
+
+[source,json]
+----
+{
+  "id": "eventCondition",
+  "type": "Condition",
+  "validation": {
+    "required": true,
+    "allowedConditionTags": ["eventCondition"]
+  }
+}
+----
+
+Example of mutually exclusive value parameters from `profilePropertyCondition`:
+
+[source,json]
+----
+{
+  "id": "propertyValue",
+  "type": "string",
+  "validation": {
+    "exclusive": true,
+    "exclusiveGroup": "propertyValue"
+  }
+},
+{
+  "id": "propertyValueInteger",
+  "type": "integer",
+  "validation": {
+    "exclusive": true,
+    "exclusiveGroup": "propertyValue"
+  }
+}
+----
+
+==== Parameter type checks
+
+Before rule-specific checks, the validation service verifies that each literal value matches the parameter `type` and `multivalued` flag.
+
+Built-in type validators cover:
+
+* `string`, `integer`, `long`, `float`, `double`, `boolean`, `date`
+* `condition` — nested condition instances (validated recursively)
+* `comparisonOperator` — custom validator for comparison operators
+
+Plugins can register additional `ValueTypeValidator` OSGi services for custom parameter types.
+
+==== Contextual parameters (partial validation)
+
+Parameters whose values start with `parameter::` or `script::` are resolved at evaluation time, not at save time.
+The validation service skips most checks on these values because the final value is unknown when the item is saved.
+
+Exceptions:
+
+* A *required* parameter that uses a contextual reference still produces a *warning* (`MISSING_RECOMMENDED_PARAMETER`) advising that static validation was skipped.
+* Nested conditions inside contextual values are not validated until runtime.
+
+This lets rules and segments use dynamic values while still validating fully literal condition trees.
+
+==== Error types
+
+Validation returns a list of `ValidationError` objects.
+Each error has a `type`, a human-readable `message`, the `parameterName`, and optional context (location, allowed values, actual value).
+
+[cols="1,2,1", options="header"]
+|===
+| Type | Meaning | Blocks save?
+
+| `MISSING_REQUIRED_PARAMETER`
+| Required parameter is absent or an empty collection
+| Yes
+
+| `INVALID_VALUE`
+| Value has wrong type, shape, or is not in `allowedValues`
+| Yes
+
+| `INVALID_CONDITION_TYPE`
+| Unknown condition type, or nested condition fails tag/type rules
+| Yes
+
+| `EXCLUSIVE_PARAMETER_VIOLATION`
+| More than one parameter in an exclusive group has a value
+| Yes
+
+| `MISSING_RECOMMENDED_PARAMETER`
+| Recommended parameter is missing, or required parameter uses a contextual reference
+| No (logged as warning)
+|===
+
+==== Save-time behavior
+
+On save, services separate *errors* from *warnings*:
+
+* *Errors* — any type except `MISSING_RECOMMENDED_PARAMETER` — cause the save to fail with an `IllegalArgumentException` or domain-specific exception (for example `BadSegmentConditionException` for segments).
+* *Warnings* — `MISSING_RECOMMENDED_PARAMETER` only — are written to the server log but do not block the save.
+
+Example error message when saving a segment with a missing required parameter:
+
+[source]
+----
+Invalid segment condition:
+- Required parameter is missing (parameter: propertyName, condition: profilePropertyCondition, location: condition[profilePropertyCondition].propertyName)
+----
+
+REST clients receive this text in the error response body when the save fails.
+
+==== Rules and invalid conditions
+
+By default, a rule with validation errors cannot be saved.
+During bundle deployment or when a rule references a missing plugin, Unomi may save the rule with `allowInvalidRules` semantics: validation errors are logged and the rule is marked invalid instead of rejected.
+This supports gradual plugin rollout without blocking server startup.
+
+==== Validation in request tracing
+
+When `explain=true` is set on context or event-collector requests, validation steps appear in the `requestTracing` tree.
+Look for operation types such as:
+
+* `condition-validation` — validation during condition type resolution
+* `segment-condition-validation` — segment save path (when triggered in that flow)
+* `rule-condition-validation` — rule condition checks
+
+Trace nodes include validation messages and whether validation passed.
+See <<_request_tracing_explain,Using the explain parameter for request tracing>> for full examples.
+
+==== Adding validation to custom condition types
+
+When you define a new condition type in a plugin:
+
+1. Declare parameters with correct `type` and `multivalued` flags.
+2. Add a `validation` block on parameters that need constraints.
+3. Use `allowedConditionTags` on nested `Condition` parameters to restrict which sub-types users can pick.
+4. Use `exclusive` / `exclusiveGroup` when several parameters represent alternative value shapes (string vs integer vs date).
+5. Register a custom `ValueTypeValidator` OSGi service if you introduce a new parameter type ID.
+
+Condition type descriptors are loaded at startup.
+After deployment, list types to confirm your validation metadata is present:
+
+[source,bash]
+----
+curl -X GET http://localhost:8181/cxs/definitions/conditions \
+  --user "TENANT_ID:PRIVATE_KEY"
+----
+
+Each condition type entry in the response includes parameter definitions with their `validation` objects, which UIs and integrators can use to guide authors before save.
+
+==== Relation to JSON Schema validation
+
+[cols="1,2,2", options="header"]
+|===
+| | Condition validation | JSON Schema validation
+
+| What is validated
+| Condition instances in segments, rules, queries
+| Event payloads on `/cxs/context.json` and `/cxs/eventcollector`
+
+| When
+| Save time (server-side)
+| Request time (public endpoints)
+
+| Defined in
+| Condition type JSON descriptors (`META-INF/cxs/conditions/`)
+| JSON schema definitions (`META-INF/cxs/json-schema/`)
+
+| REST test endpoint
+| None (save the parent item instead)
+| `/cxs/jsonSchema/validateEvent`
+|===
+
+Both systems can appear in the same `explain=true` trace: event schema validation under `event-validation`, condition checks under `condition-evaluation` or `condition-validation`.
diff --git a/manual/src/main/asciidoc/conditions-guide.adoc b/manual/src/main/asciidoc/conditions-guide.adoc
new file mode 100644
index 000000000..f6e8b871c
--- /dev/null
+++ b/manual/src/main/asciidoc/conditions-guide.adoc
@@ -0,0 +1,400 @@
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+[#_conditions_guide]
+=== Conditions guide
+
+Conditions are the building blocks of segmentation, personalization, rules, queries, campaigns, and goals in Apache Unomi.
+This guide walks from simple property checks to advanced behavioral conditions with diagrams and REST examples.
+
+See also:
+
+* <<_builtin_condition_types,Built-in condition types>> — full catalog
+* <<_condition_validation,Condition validation>> — save-time checks and parameter rules
+* <<_condition_evaluation,Condition evaluation system>> — runtime architecture
+* <<_past_event_conditions,Past event conditions>> — deep dive on behavioral conditions
+* <<_queries_and_aggregations,Queries and aggregations>> — using conditions in search APIs
+* <<_debugging_conditions,Debugging conditions>> — tracing and troubleshooting
+
+==== Concepts
+
+* *Condition type* — a reusable definition (JSON descriptor) registered at startup, for example `profilePropertyCondition`.
+* *Condition instance* — a JSON object with `type` and `parameterValues` used in segments, rules, or queries.
+* *Evaluation context* — profile, session, and/or current event, depending on where the condition runs.
+
+[plantuml]
+----
+@startuml
+skinparam componentStyle uml2
+
+package "Definitions" {
+  [Condition types\n(JSON descriptors)] as CT
+}
+
+package "Consumers" {
+  [Segments] as Seg
+  [Rules] as Rule
+  [Queries] as Q
+  [Goals / Campaigns] as GC
+}
+
+package "Runtime" {
+  [ConditionDispatcher] as CD
+  [ConditionEvaluator] as CE
+  [QueryBuilder\n(ES or OS)] as QB
+}
+
+database "Search index" as IDX
+
+CT --> Seg
+CT --> Rule
+CT --> Q
+CT --> GC
+
+Seg --> CD
+Rule --> CD
+Q --> CD
+
+CD --> CE : direct evaluation\n(profile / session / event)
+CD --> QB : query-based evaluation\n(segments, counts)
+QB --> IDX
+
+@enduml
+----
+
+==== Condition instance JSON shape
+
+Every condition instance uses the same structure:
+
+[source,json]
+----
+{
+  "type": "CONDITION_TYPE_ID",
+  "parameterValues": {
+    "parameterName": "value"
+  }
+}
+----
+
+Nested conditions use the `Condition` parameter type (for example `subConditions` on `booleanCondition`).
+
+==== Comparison operators
+
+Property conditions (`profilePropertyCondition`, `sessionPropertyCondition`, `eventPropertyCondition`) accept a `comparisonOperator` parameter.
+Common values include:
+
+`equals`, `notEquals`, `lessThan`, `greaterThan`, `lessThanOrEqualTo`, `greaterThanOrEqualTo`, `between`, `exists`, `missing`, `contains`, `notContains`, `startsWith`, `endsWith`, `matchesRegex`, `in`, `notIn`, `all`, `hasSomeOf`, `hasNoneOf`, `isDay`, `isNotDay`, `distance`.
+
+Use the typed value parameter that matches your data (`propertyValue`, `propertyValueInteger`, `propertyValues`, and so on).
+
+==== Level 1 — Profile property check
+
+Match profiles where a property equals a value. Used in segments and query counts.
+
+[source,json]
+----
+{
+  "type": "profilePropertyCondition",
+  "parameterValues": {
+    "propertyName": "properties.country",
+    "comparisonOperator": "equals",
+    "propertyValue": "US"
+  }
+}
+----
+
+Test with the query count API:
+
+[source,bash]
+----
+curl -X POST http://localhost:8181/cxs/query/profile/count \
+  --user "TENANT_ID:PRIVATE_KEY" \
+  -H "Content-Type: application/json" \
+  -d '{
+    "type": "profilePropertyCondition",
+    "parameterValues": {
+      "propertyName": "properties.country",
+      "comparisonOperator": "equals",
+      "propertyValue": "US"
+    }
+  }'
+----
+
+==== Level 2 — Event type in a rule
+
+Rules evaluate *event conditions* against the incoming event. `eventTypeCondition` is a shortcut over `eventPropertyCondition` on `eventType`.
+
+[source,json]
+----
+{
+  "type": "eventTypeCondition",
+  "parameterValues": {
+    "eventTypeId": "view"
+  }
+}
+----
+
+Use this in a rule `condition` to fire when a `view` event arrives.
+
+[plantuml]
+----
+@startuml
+actor Client
+participant "REST /cxs/eventcollector" as API
+participant "Rule engine" as Rules
+database "Profile + events" as Store
+
+Client -> API : POST event (type=view)
+API -> Rules : evaluate rule condition
+Rules -> Rules : eventTypeCondition matches?
+Rules -> Store : execute actions if true
+@enduml
+----
+
+==== Level 3 — Boolean AND / OR
+
+Combine conditions with `booleanCondition`. This is the main way to build segment definitions.
+
+[source,json]
+----
+{
+  "type": "booleanCondition",
+  "parameterValues": {
+    "operator": "and",
+    "subConditions": [
+      {
+        "type": "profilePropertyCondition",
+        "parameterValues": {
+          "propertyName": "properties.nbOfVisits",
+          "comparisonOperator": "greaterThan",
+          "propertyValueInteger": 1
+        }
+      },
+      {
+        "type": "profilePropertyCondition",
+        "parameterValues": {
+          "propertyName": "properties.country",
+          "comparisonOperator": "equals",
+          "propertyValue": "US"
+        }
+      }
+    ]
+  }
+}
+----
+
+Negate a sub-condition with `notCondition`:
+
+[source,json]
+----
+{
+  "type": "notCondition",
+  "parameterValues": {
+    "subCondition": {
+      "type": "profileSegmentCondition",
+      "parameterValues": {
+        "segments": ["opt-out-segment"],
+        "matchType": "in"
+      }
+    }
+  }
+}
+----
+
+==== Level 4 — Segment membership and session properties
+
+`profileSegmentCondition` checks whether a profile belongs to other segments (useful for segment hierarchies).
+
+[source,json]
+----
+{
+  "type": "profileSegmentCondition",
+  "parameterValues": {
+    "segments": ["high-value-customers"],
+    "matchType": "in"
+  }
+}
+----
+
+`sessionPropertyCondition` evaluates session-scoped properties during event processing:
+
+[source,json]
+----
+{
+  "type": "sessionPropertyCondition",
+  "parameterValues": {
+    "propertyName": "properties.referringCampaign",
+    "comparisonOperator": "exists"
+  }
+}
+----
+
+==== Level 5 — Past events (behavioral)
+
+`pastEventCondition` checks whether events occurred in a time window. See <<_past_event_conditions,Past event conditions>> for caching, auto-rules, and performance notes.
+
+[source,json]
+----
+{
+  "type": "pastEventCondition",
+  "parameterValues": {
+    "numberOfDays": 30,
+    "operator": "eventsOccurred",
+    "minimumEventCount": 3,
+    "eventCondition": {
+      "type": "eventTypeCondition",
+      "parameterValues": {
+        "eventTypeId": "purchase"
+      }
+    }
+  }
+}
+----
+
+==== Level 6 — Scoring, geo, and nested data
+
+*Scoring* — compare a profile score from a scoring plan:
+
+[source,json]
+----
+{
+  "type": "scoringCondition",
+  "parameterValues": {
+    "scoringPlanId": "engagement",
+    "comparisonOperator": "greaterThan",
+    "scoreValue": 50
+  }
+}
+----
+
+*Geo (session)* — `geoLocationSessionCondition` filters on country/region/city; `geoLocationByPointSessionCondition` uses map coordinates.
+
+*Nested properties* — `nestedCondition` evaluates a sub-condition on a nested path (for example list items inside profile properties).
+
+==== Using conditions in segments and rules
+
+Segments store a condition on the segment definition. Rules store a condition that is evaluated for each incoming event.
+
+[plantuml]
+----
+@startuml
+|Segment definition|
+start
+:Store condition JSON;
+:Scheduler / manual refresh;
+:QueryBuilder converts condition\nto search index query;
+:Matching profile IDs updated;
+
+|Rule definition|
+start
+:Event arrives;
+:ConditionEvaluator tests\nevent + profile + session;
+if (matches?) then (yes)
+  :Run actions;
+else (no)
+  :Skip rule;
+endif
+@enduml
+----
+
+==== Testing conditions
+
+*Query count* — verify how many profiles match (see Level 1 example).
+
+*Segment membership* — after creating a segment with your condition, test a profile:
+
+[source,bash]
+----
+curl -X GET "http://localhost:8181/cxs/segments/SEGMENT_ID/match/PROFILE_ID" \
+  --user "TENANT_ID:PRIVATE_KEY"
+----
+
+==== Discovering available condition types
+
+List registered condition types (admin or tenant private key):
+
+[source,bash]
+----
+curl -X GET http://localhost:8181/cxs/definitions/conditions \
+  --user "TENANT_ID:PRIVATE_KEY"
+----
+
+Each entry includes parameter definitions, evaluators, and query builder IDs (Unomi 3.0+ uses engine-neutral `*QueryBuilder` names).
+
+==== Custom condition types
+
+To add new condition types, implement a `ConditionEvaluator` and search-engine-specific query builders. See <<_writing_plugins,Writing plugins>> and <<_builtin_condition_types,plugin registration example>>.
+
+Add `validation` metadata on parameters so invalid instances are rejected at save time — see <<_condition_validation,Condition validation>>.
+
+[#_debugging_conditions]
+==== Debugging conditions
+
+When a segment or rule does not behave as expected, use these approaches in order.
+
+===== Test segment membership
+
+Check whether a specific profile matches a segment condition:
+
+[source,bash]
+----
+curl -X GET "http://localhost:8181/cxs/segments/SEGMENT_ID/match/PROFILE_ID" \
+  --user "TENANT_ID:PRIVATE_KEY"
+----
+
+===== Count matching profiles
+
+Use a query with your condition and read the total hit count (see Level 1 in this guide).
+
+===== Request tracing with explain
+
+For runtime debugging — which rules fired, which conditions matched, how long each step took — add `explain=true` to context or event-collector requests.
+You need an `ADMINISTRATOR` or `TENANT_ADMINISTRATOR` role.
+
+[source,bash]
+----
+curl -X POST "http://localhost:8181/cxs/context.json?sessionId=1234&explain=true" \
+  -H "X-Unomi-Api-Key: YOUR_PUBLIC_API_KEY" \
+  -H "Content-Type: application/json" \
+  -d @- <<'EOF'
+{
+  "source": { "itemId": "homepage", "itemType": "page", "scope": "mydigital" },
+  "requireSegments": true
+}
+EOF
+----
+
+The response includes a `requestTracing` tree with nodes such as `condition-evaluation`, `rule-evaluation`, and `event-validation`.
+Validation steps during processing appear as `condition-validation` or `rule-condition-validation` children.
+
+Full documentation, sample trace JSON, and sequence diagrams:
+
+* <<_request_tracing_explain,Using the explain parameter for request tracing>>
+
+TIP: Use explain only when debugging.
+It adds overhead and exposes internal processing detail to admin users.
+
+===== Fix save-time validation errors
+
+If creating or updating a segment or rule fails with `Invalid … condition`, the condition JSON does not match its type definition.
+Read the error list in the response, fix the parameter values, and retry.
+
+Common causes:
+
+* Missing `required` parameter
+* Wrong value type (string instead of integer)
+* Two parameters set in the same `exclusiveGroup`
+* Nested condition missing a required tag (for example `eventCondition` inside `pastEventCondition`)
+
+See <<_condition_validation,Condition validation>> for the full rule set and plugin author guide.
diff --git a/manual/src/main/asciidoc/configuration.adoc b/manual/src/main/asciidoc/configuration.adoc
index e9da98be8..c1318b299 100644
--- a/manual/src/main/asciidoc/configuration.adoc
+++ b/manual/src/main/asciidoc/configuration.adoc
@@ -82,6 +82,11 @@ org.apache.unomi.elasticsearch.cluster.name=contextElasticSearch
 # hostA:9200,hostB:9200
 # Note: the port number must be repeated for each host.
 org.apache.unomi.elasticsearch.addresses=localhost:9200
+# Optional Elasticsearch security (when xpack security is enabled)
+org.apache.unomi.elasticsearch.username=elastic
+org.apache.unomi.elasticsearch.password=${env:ELASTIC_PASSWORD}
+org.apache.unomi.elasticsearch.sslEnable=true
+org.apache.unomi.elasticsearch.sslTrustAllCertificates=true
 ----
 
 For OpenSearch:
@@ -111,7 +116,7 @@ To select which search engine to use, you can:
    * For OpenSearch: use `UNOMI_DISTRIBUTION=unomi-distribution-opensearch`
    * For custom configurations: use `UNOMI_DISTRIBUTION=your-custom-distribution-name`
 
-Note: The `UNOMI_DISTRIBUTION` environment variable accepts any karaf feature that is available form Karaf. Two built-in distributions (unomi-distribution-elasticsearch, unomi-distribution-opensearch) are packaged with UNOMI archive but any other can be added, either in the package  or by adding a feature repository that contains the desired one in Karaf.
+Note: The `UNOMI_DISTRIBUTION` environment variable accepts any karaf feature that is available from Karaf. Two built-in distributions (unomi-distribution-elasticsearch, unomi-distribution-opensearch) are packaged with UNOMI archive but any other can be added, either in the package  or by adding a feature repository that contains the desired one in Karaf.
 
 Note: When using OpenSearch 3.x:
  - Security is enabled by default and requires SSL/TLS
@@ -282,10 +287,10 @@ curl -X POST "http://localhost:8181/cxs/tenants" \
   -u karaf:karaf \
   -H "Content-Type: application/json" \
   -d '{
-    "itemId": "mytenant",
-    "name": "My Company",
-    "description": "My Company tenant",
+    "requestedId": "mytenant",
     "properties": {
+      "name": "My Company",
+      "description": "My Company tenant",
       "address": "123 Main St",
       "country": "USA"
     }
@@ -293,7 +298,7 @@ curl -X POST "http://localhost:8181/cxs/tenants" \
 
 # Response (HTTP 201 Created):
 {
-    "itemId": "mytenant",
+    "requestedId": "mytenant",
     "name": "My Company",
     "description": "My Company tenant",
     "properties": {
@@ -322,20 +327,21 @@ curl -X DELETE "http://localhost:8181/cxs/tenants/mytenant" \
   -u karaf:karaf
 ----
 
-Using Karaf shell (requires admin access to Karaf console):
+Using Karaf shell (requires admin access to Karaf console). See <<_shell_commands,Shell commands>> for full syntax:
+
 [source,bash]
 ----
-# Create a tenant
-unomi:tenant-create mytenant "My Company" --description="My Company tenant"
+# Create a tenant (JSON properties)
+unomi:crud create tenant -d '{"itemId":"mytenant","name":"My Company","description":"My Company tenant"}'
 
-# List all tenants
-unomi:tenant-list
+# List tenants
+unomi:crud list tenant
 
 # View tenant details
-unomi:tenant-show mytenant
+unomi:crud read tenant -i mytenant
 
 # Delete a tenant
-unomi:tenant-delete mytenant
+unomi:crud delete tenant -i mytenant
 ----
 
 ==== API Keys and Authentication
@@ -348,7 +354,7 @@ The API keys are automatically generated when creating a tenant. You can view th
 [source,bash]
 ----
 # Using Karaf shell (requires admin access)
-unomi:tenant-show mytenant
+unomi:crud read tenant -i mytenant
 
 # Output example:
 Tenant Details:
@@ -379,7 +385,7 @@ curl -X POST "http://localhost:8181/cxs/tenants/mytenant/apikeys?type=PUBLIC&val
 }
 
 # Using Karaf shell (requires admin access)
-unomi:tenant-generate-key mytenant PUBLIC 30
+unomi:crud create apikey -d '{"tenantId":"mytenant","keyType":"PUBLIC","validityPeriod":30}'
 ----
 
 ==== Accessing API Endpoints
@@ -733,7 +739,7 @@ org.apache.unomi.security.personalization.sanitizeConditions=${env:UNOMI_SECURIT
 
 Groovy actions offer the ability to define a set of actions and action types (aka action descriptors) purely from Groovy scripts defined at runtime.
 
-Initially submitted to Unomi through a purpose-built REST API endpoint, Groovy actions are then stored in Elasticsearch. When an event matches a rule configured to execute an action, the corresponding action is fetched from Elasticsearch and executed.
+Initially submitted to Unomi through a purpose-built REST API endpoint, Groovy actions are then stored in the persistence layer (Elasticsearch or OpenSearch). When an event matches a rule configured to execute an action, the corresponding action is fetched from persistence and executed.
 
 ===== Anatomy of a Groovy Action
 
@@ -1468,6 +1474,8 @@ Be aware that in distribution's feature, you should only reference other feature
         unomi-wab
         unomi-web-tracker
         unomi-healthcheck-elasticsearch
+NOTE: For OpenSearch distributions, use `unomi-healthcheck-opensearch` instead of `unomi-healthcheck-elasticsearch` in custom feature XML.
+
         unomi-router-karaf-feature
         unomi-groovy-actions
         unomi-rest-ui
@@ -1648,7 +1656,7 @@ services:
     depends_on:
       - elasticsearch
   elasticsearch:
-    image: docker.elastic.co/elasticsearch/elasticsearch:9.15.0
+    image: docker.elastic.co/elasticsearch/elasticsearch:9.4.3
     environment:
       - discovery.type=single-node
       - xpack.security.enabled=false
@@ -1716,7 +1724,7 @@ services:
     depends_on:
       - elasticsearch
   elasticsearch:
-    image: docker.elastic.co/elasticsearch/elasticsearch:9.15.0
+    image: docker.elastic.co/elasticsearch/elasticsearch:9.4.3
     environment:
       - discovery.type=single-node
       - xpack.security.enabled=false
@@ -1743,6 +1751,7 @@ cd apache-unomi-custom-*
 unomi:start elasticsearch-prod
 ----
 
+[#_health_check]
 === Health Check Extension
 
 The Health Check extension provides a way to check is required Unomi components are 'live'.
@@ -1766,8 +1775,8 @@ Existing health checks gives information about :
 
 All healthcheck can have a status :
 - DOWN (service is not available)
-- UP (service is up but does not respond to request (starting or misconfigured))
-- LIVE (service is ready to serve request)
+- UP (service is running or starting)
+- LIVE (service is ready to serve requests)
 - ERROR (an error occurred during service health check)
 
 Any subsystem health check have a timeout of 400ms where check is cancelled and will be returned as error.
diff --git a/manual/src/main/asciidoc/connectors/salesforce-connector.adoc b/manual/src/main/asciidoc/connectors/salesforce-connector.adoc
index 73998c96e..2ded160cc 100644
--- a/manual/src/main/asciidoc/connectors/salesforce-connector.adoc
+++ b/manual/src/main/asciidoc/connectors/salesforce-connector.adoc
@@ -11,7 +11,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 //
-[[_salesforce_connector]]
+[#_salesforce_connector]
 === Salesforce Connector
 
 This connectors makes it possible to push and pull data to/from the Salesforce CRM. It can copy information between
diff --git a/manual/src/main/asciidoc/consent-api.adoc b/manual/src/main/asciidoc/consent-api.adoc
index ad23f7ac5..b9107b0f4 100644
--- a/manual/src/main/asciidoc/consent-api.adoc
+++ b/manual/src/main/asciidoc/consent-api.adoc
@@ -11,7 +11,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 //
-[[_consent_api]]
+[#_consent_api]
 === Consent API
 
 Starting with Apache Unomi 1.3, a new API for consent management is now available. This API
diff --git a/manual/src/main/asciidoc/datamodel.adoc b/manual/src/main/asciidoc/datamodel.adoc
index 2f4d17e0a..f22571bd2 100755
--- a/manual/src/main/asciidoc/datamodel.adoc
+++ b/manual/src/main/asciidoc/datamodel.adoc
@@ -12,7 +12,7 @@
 // limitations under the License.
 //
 
-[[_data_model_overview]]
+[#_data_model_overview]
 === Data Model Overview
 
 Apache Unomi gathers information about users actions, information that is processed and stored by Unomi services.
@@ -60,6 +60,20 @@ This base structure can be extended, if needed, using properties in the form of
 These properties are further defined by the `Item`’s type definition which explicits the `Item`’s structure and semantics.
 By defining new types, users specify which properties (including the type of values they accept) are available to items of that specific type.
 
+
+[#_tenant_entity]
+=== Tenant (Unomi 3.1+)
+
+A *tenant* is the top-level isolation boundary in Unomi 3.1. Each tenant owns its own profiles, events, segments, rules, property types, and JSON schemas. Tenant metadata is stored as an `Item` with `itemType` `tenant`.
+
+Most persisted items carry a `tenantId` (or inherit tenant scope through the execution context). Client requests must establish tenant context through:
+
+* `X-Unomi-Api-Key` (public key resolves the tenant), or
+* Basic authentication `tenantId:privateApiKey`, or
+* JAAS administrator credentials with `X-Unomi-Tenant-Id`.
+
+See <<_multitenancy,Multi-tenancy>> for management and authentication.
+
 Unomi defines default value types: `date`, `email`, `integer` and `string`, all pretty self-explanatory.
 While you can think of these value types as "primitive" types, it is possible to extend Unomi by providing additional value types.
 
@@ -613,7 +627,7 @@ The visitor’s location is also resolve based on the IP address that was used t
 }
 ----
 
-[[_segment]]
+[#_segment]
 === Segment
 
 Segments are used to group profiles together, and are based on conditions that are executed on profiles to determine
@@ -732,7 +746,7 @@ The result of a condition is always a boolean value of true or false.
 Apache Unomi provides quite a lot of built-in condition types, including boolean types that make it possible to compose conditions using operators such as `and`, `or` or `not`.
 Composition is an essential element of building more complex conditions.
 
-For a more complete list of available condition types, see the <<_builtin_condition_types,Built-in condition types>> reference section.
+For tutorials and examples, see <<_conditions_guide,Conditions guide>>. For the full catalog, see <<_builtin_condition_types,Built-in condition types>>.
 
 ==== Structure definition
 
diff --git a/manual/src/main/asciidoc/docinfo-footer.html b/manual/src/main/asciidoc/docinfo-footer.html
new file mode 100644
index 000000000..14f82d1cd
--- /dev/null
+++ b/manual/src/main/asciidoc/docinfo-footer.html
@@ -0,0 +1,67 @@
+
+
diff --git a/manual/src/main/asciidoc/docinfo.html b/manual/src/main/asciidoc/docinfo.html
new file mode 100644
index 000000000..b2d477a46
--- /dev/null
+++ b/manual/src/main/asciidoc/docinfo.html
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
diff --git a/manual/src/main/asciidoc/foreword.adoc b/manual/src/main/asciidoc/foreword.adoc
index 19fcbeec9..eaaebb856 100644
--- a/manual/src/main/asciidoc/foreword.adoc
+++ b/manual/src/main/asciidoc/foreword.adoc
@@ -12,7 +12,7 @@
 // limitations under the License.
 //
 
-[[_foreword]]
+[#_foreword]
 == Foreword
 
 This documentation provides a comprehensive guide to Apache Unomi 3.x, a powerful and flexible Customer Data Platform (CDP) that enables organizations to collect, manage, and leverage customer data for personalization and marketing automation.
@@ -24,3 +24,5 @@ This guide covers everything from getting started with your first installation t
 The Apache Unomi community is committed to providing comprehensive, accurate, and up-to-date documentation. We welcome feedback and contributions to help improve this documentation for all users.
 
 Happy reading!
+
+This edition documents Apache Unomi 3.x, which requires Java 17, Elasticsearch 9.x or OpenSearch 3.x, and (from 3.1) tenant API keys for public endpoints.
diff --git a/manual/src/main/asciidoc/getting-started.adoc b/manual/src/main/asciidoc/getting-started.adoc
index c7acd9121..ac9fe5b09 100644
--- a/manual/src/main/asciidoc/getting-started.adoc
+++ b/manual/src/main/asciidoc/getting-started.adoc
@@ -11,7 +11,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 //
-[[_getting_started_with_unomi]]
+[#_getting_started_with_unomi]
 === Getting started with Unomi
 
 We will first get you up and running with an example. We will then lift the corner of the cover somewhat and explain
@@ -22,7 +22,7 @@ in greater details what just happened.
 This document assumes working knowledge of https://git-scm.com/[git] to be able to retrieve the code for Unomi and the example.
 Additionally, you will require a working Java 17 or above install. Refer to http://www.oracle.com/technetwork/java/javase/[http://www.oracle.com/technetwork/java/javase/] for details on how to download and install Java SE 17 or greater.
 
-[[_jdk_compatibility]]
+[#_jdk_compatibility]
 ===== JDK compatibility
 
 Starting with Java 9, Oracle made some big changes to the Java platform releases. This is why Apache Unomi is focused on
@@ -37,8 +37,8 @@ them at your own risks.
 
 Apache Unomi supports two search engine backends:
 
-* *ElasticSearch*: Version 7.17.5 is supported starting with Unomi 2.0.0
-* *OpenSearch*: Version 3.x is supported
+* *ElasticSearch*: Version **9.x** (for example 9.4.3) is required for Unomi 3.0 and later. Version 7.17.5 applies only to Unomi 2.x.
+* *OpenSearch*: Version 3.x is supported starting with Unomi 3.1
 
 It is highly recommended to use the versions specified in the documentation. When in doubt, consult the Apache Unomi community
 for the latest compatibility information.
@@ -85,7 +85,7 @@ curl -X POST http://localhost:8181/cxs/tenants \
   }'
 ----
 
-Save the API keys from the response - you'll need them for all subsequent API calls.
+Save the API keys from the response - you'll need them for all subsequent API calls. See <<_multitenancy,Multi-tenancy>> for authentication details.
 
 Now you can:
 
diff --git a/manual/src/main/asciidoc/graphql.adoc b/manual/src/main/asciidoc/graphql.adoc
index 54da2adaf..3824517e2 100644
--- a/manual/src/main/asciidoc/graphql.adoc
+++ b/manual/src/main/asciidoc/graphql.adoc
@@ -36,6 +36,17 @@ Thanks to GraphQL introspection, there is no dedicated documentation per-se as t
 You can easily view the schema by navigrating to `/graphql-ui`, depending on your setup (localhost, public host, ...),
 you might need to adjust the URL to point GraphQL UI to the `/graphql` endpoint.
 
+
+==== Authentication
+
+GraphQL requests follow the same tenant security model as REST:
+
+* *Public read operations* on the `cdp` root field: send `X-Unomi-Api-Key` with a tenant **public** API key.
+* *Mutations and administrative queries*: HTTP Basic authentication with `tenantId:privateApiKey`, or JAAS credentials plus optional `X-Unomi-Tenant-Id`.
+* *Introspection* (`IntrospectionQuery`): allowed without credentials for schema discovery.
+
+See <<_multitenancy,Multi-tenancy>> for key management.
+
 === Multi-Tenancy Support
 
 The GraphQL API includes comprehensive multi-tenancy support, allowing different tenants to have customized GraphQL schemas based on their specific data models and property types.
diff --git a/manual/src/main/asciidoc/how-profile-tracking-works.adoc b/manual/src/main/asciidoc/how-profile-tracking-works.adoc
index db3e9ebd4..587ba0f9b 100644
--- a/manual/src/main/asciidoc/how-profile-tracking-works.adoc
+++ b/manual/src/main/asciidoc/how-profile-tracking-works.adoc
@@ -11,7 +11,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 //
-[[_how_profile_tracking_works]]
+[#_how_profile_tracking_works]
 === How profile tracking works
 
 In this section you will learn how Apache Unomi keeps track of visitors and processes context requests.
diff --git a/manual/src/main/asciidoc/images/apache-unomi-logo.png b/manual/src/main/asciidoc/images/apache-unomi-logo.png
new file mode 100644
index 000000000..3da06424f
Binary files /dev/null and b/manual/src/main/asciidoc/images/apache-unomi-logo.png differ
diff --git a/manual/src/main/asciidoc/images/unomi-favicon.png b/manual/src/main/asciidoc/images/unomi-favicon.png
new file mode 100644
index 000000000..09052eee3
Binary files /dev/null and b/manual/src/main/asciidoc/images/unomi-favicon.png differ
diff --git a/manual/src/main/asciidoc/index.adoc b/manual/src/main/asciidoc/index.adoc
index f5cfa4a44..5cda16588 100644
--- a/manual/src/main/asciidoc/index.adoc
+++ b/manual/src/main/asciidoc/index.adoc
@@ -21,12 +21,62 @@ Apache Software Foundation
 :toc-title: Table of Contents
 :numbered:
 :homepage: https://unomi.apache.org
-
-image::asf_logo_url.png[pdfwidth=35%,align=center]
+:docinfo: shared-head,shared-footer
+:source-highlighter: highlightjs
+
+ifndef::backend-pdf[]
+[%passthrough,subs=attributes]
+++++
+
+ +
+++++ + +[.hero.cover-page] +-- +[.section-label] +Apache Unomi™ + +++++ +

Apache Unomi 3.x
Documentation

+++++ + +[.cover-meta.lead] +Version {revnumber} · link:https://unomi.apache.org[unomi.apache.org] · link:https://github.com/apache/unomi/tree/master/manual/src/main/asciidoc[Edit on GitHub] + +[.cover-asf] +image::asf_logo_url.png[Apache Software Foundation,width=140,align=center] +-- +endif::[] + +ifdef::backend-pdf[] +image::asf_logo_url.png[Apache Software Foundation,pdfwidth=25%,align=center,role=pdf-asf-mark] +endif::[] include::foreword.adoc[] -[[_whats_new]] +[#_whats_new] == What's new include::whats-new.adoc[] @@ -45,28 +95,45 @@ include::recipes.adoc[] include::request-examples.adoc[] -[[_configuration]] +[#_configuration] == Configuration include::configuration.adoc[] -[[_json_schemas]] +[#_multitenancy] +== Multi-tenancy + +include::multitenancy.adoc[] + +[#_json_schemas] == JSON schemas include::jsonSchema/json-schema.adoc[] -[[_graphql_api]] +[#_graphql_api] == GraphQL API include::graphql.adoc[] include::graphql-examples.adoc[] -[[_migrations]] +[#_migrations] == Migrations include::migrations/migrations.adoc[] +[#_conditions] +== Conditions + +include::conditions-guide.adoc[] + +include::condition-validation.adoc[] + +include::condition-evaluation.adoc[] + +include::past-event-conditions.adoc[] + +[#_queries_and_aggregations] == Queries and aggregations include::queries-and-aggregations.adoc[] @@ -87,6 +154,16 @@ include::privacy.adoc[] include::clustering.adoc[] +[#_scheduler] +== Task scheduler + +include::scheduler.adoc[] + +[#_health_check_chapter] +== Health check + +The health check endpoint and providers are documented in the Configuration chapter: <<_health_check,Health check extension>>. + == Reference === Architecture Overview @@ -120,13 +197,12 @@ include::builtin-action-types.adoc[] include::updating-events.adoc[] -include::past-event-conditions.adoc[] include::web-tracker.adoc[] include::javascript-tracker-guide.adoc[] -[[_integration_samples]] +[#_integration_samples] == Integration samples include::samples/samples.adoc[] diff --git a/manual/src/main/asciidoc/jsonSchema/extend-an-existing-schema.adoc b/manual/src/main/asciidoc/jsonSchema/extend-an-existing-schema.adoc index 204da1dff..6a147577d 100644 --- a/manual/src/main/asciidoc/jsonSchema/extend-an-existing-schema.adoc +++ b/manual/src/main/asciidoc/jsonSchema/extend-an-existing-schema.adoc @@ -12,7 +12,7 @@ // limitations under the License. // -[[_extend_an_existing_schema]] +[#_extend_an_existing_schema] === Extend an existing schema ==== When a extension is needed? diff --git a/manual/src/main/asciidoc/jsonSchema/json-schema-api.adoc b/manual/src/main/asciidoc/jsonSchema/json-schema-api.adoc index 1b908c206..2d4a01440 100644 --- a/manual/src/main/asciidoc/jsonSchema/json-schema-api.adoc +++ b/manual/src/main/asciidoc/jsonSchema/json-schema-api.adoc @@ -71,7 +71,7 @@ curl --location --request POST 'http://localhost:8181/cxs/jsonSchema/query' \ NOTE: Replace `TENANT_ID` and `PRIVATE_KEY` with your actual tenant ID and private API key. You can obtain these when creating a tenant via the Tenant API (which requires system administrator authentication). -[[_create_update_json_schema]] +[#_create_update_json_schema] ==== Create / update a JSON schema to validate an event It’s possible to add or update JSON schema by calling the endpoint `POST {{url}}/cxs/jsonSchema` with the JSON schema in the payload of the request. diff --git a/manual/src/main/asciidoc/migrations/migrate-2.x-to-3.0.adoc b/manual/src/main/asciidoc/migrations/migrate-2.x-to-3.0.adoc index 8b58df24e..5dd4e2da1 100644 --- a/manual/src/main/asciidoc/migrations/migrate-2.x-to-3.0.adoc +++ b/manual/src/main/asciidoc/migrations/migrate-2.x-to-3.0.adoc @@ -12,6 +12,7 @@ // limitations under the License. // +[#_migrate_from_2_x_to_3_0] ==== Migration Overview Apache Unomi 3.0 is a major release that introduces several breaking changes. This document details the steps required to successfully migrate your environment from Apache Unomi 2.x to Apache Unomi 3.0. @@ -42,7 +43,7 @@ NOTE: OpenSearch support is introduced in Apache Unomi 3.1. If you plan to use O Apache Unomi 3.0 includes an upgrade of the underlying Karaf framework: - **Previous Version**: Karaf 4.2.15 -- **New Version**: Karaf 4.4.8 +- **New Version**: Karaf 4.4.11 This upgrade brings improved stability and support for the latest dependencies. @@ -265,7 +266,7 @@ When using Docker, you can specify the search engine backend using the distribut Apache Unomi 3.1 introduces official support for OpenSearch as an alternative to Elasticsearch. If you want to migrate from Elasticsearch to OpenSearch, you can use the dedicated migration guide located in the 3.0 → 3.1 migration section. -For detailed instructions on migrating your data from Elasticsearch to OpenSearch, please refer to the <<_migrating_from_elasticsearch_to_opensearch,Elasticsearch to OpenSearch migration guide>>. +For detailed instructions on migrating your data from Elasticsearch to OpenSearch, please refer to the <<_migrate_from_elasticsearch_to_opensearch,Elasticsearch to OpenSearch migration guide>>. ==== Migration Checklist diff --git a/manual/src/main/asciidoc/migrations/migrate-3.0-to-3.1.adoc b/manual/src/main/asciidoc/migrations/migrate-3.0-to-3.1.adoc index 92961678c..a107ce7a7 100644 --- a/manual/src/main/asciidoc/migrations/migrate-3.0-to-3.1.adoc +++ b/manual/src/main/asciidoc/migrations/migrate-3.0-to-3.1.adoc @@ -12,6 +12,7 @@ // limitations under the License. // +[#_migrate_from_3_0_to_3_1] === Migration Overview Apache Unomi 3.1 introduces comprehensive multi-tenancy support, enabling complete data isolation between different tenants. This fundamental architectural change requires a new tenant-based authentication model while keeping all API endpoints unchanged. @@ -245,65 +246,29 @@ Contributors maintaining the Groovy scripts that implement this upgrade should f 4. **Start your Apache Unomi 3.1 cluster** 5. **Test your applications** with the new authentication model -=== 3.0 Compatibility Mode +=== Transitional authentication options -To facilitate the migration process, Unomi 3.1 includes a **3.0 compatibility mode** that allows 3.0 client applications to work with Unomi 3.1 without immediate code changes. +Unomi 3.1 does **not** provide a separate "3.0 compatibility mode" system property. Use one of the following approaches while migrating clients: -==== Enabling 3.0 Compatibility Mode +==== Option 1: Update clients to 3.1 authentication (recommended) -To enable 3.0 compatibility mode, set the following system property when starting Unomi 3.1: +Adopt tenant public API keys for public endpoints and tenant private keys or JAAS for administrative work. See <<_multitenancy,Multi-tenancy>> and the authentication examples in this guide. -[source,bash] ----- -# Enable 3.0 compatibility mode --Dunomi.v3_0.compatibility.mode=true ----- - -==== 3.0 Compatibility Mode Behavior - -When 3.0 compatibility mode is enabled: - -- **Public endpoints** (e.g., `/context.json`): No authentication required (same as 3.0) -- **Private endpoints**: JAAS authentication required (same as 3.0) -- **All API endpoints**: Identical behavior to 3.0 -- **Data isolation**: Still enforced through tenant context - -==== Migration Strategy with 3.0 Compatibility Mode - -1. **Phase 1: Enable 3.0 Compatibility Mode** - - Start Unomi 3.1 with `-Dunomi.v3_0.compatibility.mode=true` - - Verify all 3.0 client applications work without changes - - Migrate data to tenant structure +==== Option 2: V2 compatibility mode (2.x clients only) -2. **Phase 2: Gradual Migration** - - Update client applications one by one to use 3.1 authentication - - Test each application with 3.1 authentication - - Keep 3.0 compatibility mode enabled for remaining applications +If you are migrating from **Unomi 2.x** (not 3.0), you can enable <<_v2_compatibility_mode,V2 compatibility mode>> so legacy clients work without API keys during a phased rollout: -3. **Phase 3: Complete Migration** - - Update all client applications to 3.1 authentication - - Disable 3.0 compatibility mode: `-Dunomi.v3_0.compatibility.mode=false` - - Verify all applications work with full 3.1 multi-tenancy - -==== Security Considerations - -- **3.0 compatibility mode** should only be used during migration -- **Production environments** should use full 3.1 authentication for security -- **3.0 compatibility mode** bypasses tenant API key requirements -- **Data isolation** is still enforced through tenant context - -==== Example: Starting Unomi 3.1 with 3.0 Compatibility Mode - -[source,bash] +[source,properties] ---- -# Start Unomi 3.1 with 3.0 compatibility mode -./karaf -Dunomi.v3_0.compatibility.mode=true - -# Or set as environment variable -export KARAF_OPTS="-Dunomi.v3_0.compatibility.mode=true" -./karaf +# etc/org.apache.unomi.rest.authentication.cfg +v2.compatibilitymode.enabled = true +v2.compatibilitymode.defaultTenantId = default ---- +Environment variable equivalent: `UNOMI_REST_AUTHENTICATION_V2COMPATIBILITYMODEENABLED=true` + +WARNING: V2 compatibility mode is intended for migration from 2.x only. It bypasses tenant API key requirements on public endpoints. Disable it once all clients use 3.1 authentication. + === Post Migration Once the migration has been completed, you will be able to start Apache Unomi 3.1 with full multi-tenancy support. diff --git a/manual/src/main/asciidoc/migrations/migrate-elasticsearch-to-opensearch.adoc b/manual/src/main/asciidoc/migrations/migrate-elasticsearch-to-opensearch.adoc index 06316e129..b68e29ac3 100644 --- a/manual/src/main/asciidoc/migrations/migrate-elasticsearch-to-opensearch.adoc +++ b/manual/src/main/asciidoc/migrations/migrate-elasticsearch-to-opensearch.adoc @@ -12,7 +12,7 @@ // limitations under the License. // -[[_migrating_from_elasticsearch_to_opensearch]] +[#_migrate_from_elasticsearch_to_opensearch] ==== Migrating from Elasticsearch to OpenSearch :toc: macro :toclevels: 4 @@ -32,7 +32,7 @@ Before starting the migration, ensure you have: * Running Elasticsearch cluster with your Unomi data * Target OpenSearch cluster set up and running * Sufficient disk space on the target cluster -* Java 11 or later installed +* Java 17 or later installed * Network connectivity between source and target clusters ===== Migration Options diff --git a/manual/src/main/asciidoc/migrations/migrate-es7-to-es9.adoc b/manual/src/main/asciidoc/migrations/migrate-es7-to-es9.adoc index 4afcdf17e..8e03b788f 100644 --- a/manual/src/main/asciidoc/migrations/migrate-es7-to-es9.adoc +++ b/manual/src/main/asciidoc/migrations/migrate-es7-to-es9.adoc @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // -[[_migrate_from_elasticsearch_7_to_elasticsearch_9]] +[#_migrate_from_elasticsearch_7_to_elasticsearch_9] ==== Migrate from Elasticsearch 7 to Elasticsearch 9 You can use the *remote reindex* API to upgrade directly from Elasticsearch 7 to Elasticsearch 9. This approach runs both clusters in parallel and uses Elasticsearch's remote reindex feature. diff --git a/manual/src/main/asciidoc/migrations/migrations.adoc b/manual/src/main/asciidoc/migrations/migrations.adoc index b096abe8a..b5a24d4c6 100644 --- a/manual/src/main/asciidoc/migrations/migrations.adoc +++ b/manual/src/main/asciidoc/migrations/migrations.adoc @@ -14,16 +14,12 @@ This section contains information and steps to migrate between major Unomi versions. -=== Writing migration scripts - include::writing-migration-scripts.adoc[] === V2/V3 API Compatibility Guide include::v2-v3-compatibility.adoc[] -=== V2 Compatibility Mode - include::v2-compatibility-mode.adoc[] === From version 3.0 to 3.1 diff --git a/manual/src/main/asciidoc/migrations/v2-compatibility-mode.adoc b/manual/src/main/asciidoc/migrations/v2-compatibility-mode.adoc index a1cc67efc..4a7e54f30 100644 --- a/manual/src/main/asciidoc/migrations/v2-compatibility-mode.adoc +++ b/manual/src/main/asciidoc/migrations/v2-compatibility-mode.adoc @@ -12,7 +12,8 @@ // limitations under the License. // -= Apache Unomi V2 Compatibility Mode +[#_v2_compatibility_mode] +=== V2 compatibility mode This document explains how to use the V2 compatibility mode in Apache Unomi V3, which allows V2 client applications to work with Unomi V3 without requiring API keys. @@ -44,6 +45,8 @@ Before enabling V2 compatibility mode, ensure that: === Enable V2 Compatibility Mode +You can also set the environment variable `UNOMI_REST_AUTHENTICATION_V2COMPATIBILITYMODEENABLED=true` (maps to `org.apache.unomi.rest.authentication.v2CompatibilityModeEnabled` in `custom.system.properties`). + 1. **Edit the configuration file**: ```bash # Edit the configuration file @@ -53,10 +56,10 @@ Before enabling V2 compatibility mode, ensure that: 2. **Enable V2 compatibility mode**: ```properties # Enable V2 compatibility mode - v2CompatibilityModeEnabled = true + v2.compatibilitymode.enabled = true # Set the default tenant ID (should match the tenant ID used during migration) - v2CompatibilityDefaultTenantId = your-migration-tenant-id + v2.compatibilitymode.defaultTenantId = your-migration-tenant-id ``` 3. **Restart the server** to apply the configuration changes: @@ -94,7 +97,7 @@ Enable V2 compatibility mode by updating the configuration file: # Edit the configuration file vi etc/org.apache.unomi.rest.authentication.cfg -# Set v2CompatibilityModeEnabled = true +# Set v2.compatibilitymode.enabled = true # Set v2CompatibilityDefaultTenantId = your-tenant-id # Restart the server to apply changes @@ -253,7 +256,7 @@ The V2 third-party configuration supports dynamic updates: **V2 clients still not working**: - Check configuration file: `etc/org.apache.unomi.rest.authentication.cfg` -- Verify `v2CompatibilityModeEnabled = true` +- Verify `v2.compatibilitymode.enabled = true` - Ensure `v2CompatibilityDefaultTenantId` matches the tenant ID used during migration - Ensure the tenant exists and is accessible @@ -289,7 +292,7 @@ Once all clients are migrated to V3 authentication: 1. **Update configuration**: ```properties - v2CompatibilityModeEnabled = false + v2.compatibilitymode.enabled = false ``` 2. **Restart the server**: diff --git a/manual/src/main/asciidoc/migrations/writing-migration-scripts.adoc b/manual/src/main/asciidoc/migrations/writing-migration-scripts.adoc index b0615bb5d..64f543ae0 100644 --- a/manual/src/main/asciidoc/migrations/writing-migration-scripts.adoc +++ b/manual/src/main/asciidoc/migrations/writing-migration-scripts.adoc @@ -12,7 +12,7 @@ // limitations under the License. // -[[_writing_migration_scripts]] +[#_writing_migration_scripts] === Writing migration scripts This section is for **contributors** who add or change Groovy migration scripts shipped in the `shell-commands` module. diff --git a/manual/src/main/asciidoc/multitenancy.adoc b/manual/src/main/asciidoc/multitenancy.adoc index 72c77cdc2..e91c30e29 100644 --- a/manual/src/main/asciidoc/multitenancy.adoc +++ b/manual/src/main/asciidoc/multitenancy.adoc @@ -12,12 +12,10 @@ // limitations under the License. // -= Apache Unomi Multi-tenancy -:toc: macro -:toclevels: 4 -:toc-title: Table of contents +=== Multi-tenancy guide -toc::[] +Apache Unomi 3.1+ isolates profiles, events, segments, rules, and schemas per tenant. +Each tenant has public and private API keys. See also <<_migrate_from_3_0_to_3_1,Migrate from 3.0 to 3.1>> for authentication changes. == Overview @@ -30,7 +28,7 @@ Apache Unomi provides robust multi-tenancy support, allowing multiple organizati * Tenant-specific configuration * Per-tenant usage metrics (quotas enforced upstream) * Migration tools for existing data -* Support for both REST and GraphQL APIs +* Support for REST APIs (and GraphQL when enabled) == Authentication Methods @@ -140,11 +138,11 @@ tenant.default.id=default tenant.apikey.validity.period=30 tenant.apikey.validity.unit=DAYS -# Maximum number of API calls per tenant per day -tenant.apikey.maxCalls=100000 +# Warn this many days before an API key expires +tenant.apikey.rotation.warning.days=7 -# Enable/disable tenant isolation -tenant.isolation.enabled=true +# Prefix for tenant-specific security roles +tenant.security.roles.prefix=unomi_tenant_ ---- === Security Provider Configuration @@ -257,11 +255,12 @@ To define custom operations: Example: 1. Add to `etc/org.apache.unomi.security.cfg`: ++ [source,properties] ---- operation.roles.CUSTOM_OPERATION=ROLE_UNOMI_TENANT_ADMIN ---- - ++ 2. Use in your code: [source,java] ---- @@ -772,7 +771,7 @@ curl -X POST http://localhost:8181/cxs/context.json \ }] }' ---- - ++ 2. Verify profile properties were updated: [source,bash] ---- diff --git a/manual/src/main/asciidoc/past-event-conditions.adoc b/manual/src/main/asciidoc/past-event-conditions.adoc index 58abdf7bd..d038f263b 100644 --- a/manual/src/main/asciidoc/past-event-conditions.adoc +++ b/manual/src/main/asciidoc/past-event-conditions.adoc @@ -12,6 +12,7 @@ // limitations under the License. // +[#_past_event_conditions] === Past Event Conditions in Apache Unomi :toc: macro :toclevels: 4 @@ -405,11 +406,11 @@ skinparam component { package "Past Event System" { [SetEventOccurenceCountAction] as action [PastEventConditionEvaluator] as evaluator - [PastEventConditionESQueryBuilder] as queryBuilder + [pastEventConditionQueryBuilder] as queryBuilder [SegmentServiceImpl] as segmentService } -database "Elasticsearch" { +database "Search index (Elasticsearch or OpenSearch)" as searchIndex { [Events] as events [Profiles] as profiles } @@ -749,7 +750,7 @@ The past event condition system consists of four main components: 1. `SetEventOccurenceCountAction` - Handles real-time event processing and updates profile event counts 2. `PastEventConditionEvaluator` - Manages condition evaluation with optimized caching -3. `PastEventConditionESQueryBuilder` - Constructs optimized Elasticsearch queries +3. `pastEventConditionQueryBuilder` - Constructs optimized search index queries 4. `SegmentServiceImpl` - Manages segments and auto-generated rules == Condition Parameters @@ -837,7 +838,7 @@ When an event occurs in Unomi, the `SetEventOccurenceCountAction` processes it i * The original event condition * Time window constraints * Profile filters -3. Queries existing matching events using optimized Elasticsearch queries +3. Queries existing matching events using optimized search index queries 4. Updates the profile's past event count in system properties 5. Triggers profile update events if configured @@ -937,8 +938,8 @@ The evaluation process involves multiple components working together to efficien ** For `eventsOccurred`: Verifies count is between `minimumEventCount` and `maximumEventCount` ** For `eventsNotOccurred`: Verifies count is exactly 0 -2. *Query Builder* (`PastEventConditionESQueryBuilder`) - * Responsible for constructing optimized Elasticsearch queries +2. *Query Builder* (`pastEventConditionQueryBuilder`) + * Responsible for constructing optimized search index queries * Supports two query strategies: ** Property-based queries: Uses cached counts from profile properties ** Event-based queries: Aggregates events directly when needed diff --git a/manual/src/main/asciidoc/patches.adoc b/manual/src/main/asciidoc/patches.adoc index 9425df560..7e4d94370 100644 --- a/manual/src/main/asciidoc/patches.adoc +++ b/manual/src/main/asciidoc/patches.adoc @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // -[[_migration_patches]] +[#_migration_patches] === Migration patches You may provide patches on any predefined items by simply adding a JSON file in : diff --git a/manual/src/main/asciidoc/privacy.adoc b/manual/src/main/asciidoc/privacy.adoc index bbde3c398..3384ba6be 100644 --- a/manual/src/main/asciidoc/privacy.adoc +++ b/manual/src/main/asciidoc/privacy.adoc @@ -34,11 +34,15 @@ geographic location) It is possible to anonymize a profile, meaning it will remove all "identifying" property values from the profile. Basically all properties with the tag `personalIdentifierProperties` will be purged from the profile. + +NOTE: In Unomi 3.1+, `/cxs/privacy/*` endpoints require tenant or administrator authentication. The examples below use a tenant private key; you may also proxy these endpoints and validate the profile cookie server-side as described above. + Here's an example of a request to anonymize a profile: -[source] +[source,bash] ---- -curl -X POST http://localhost:8181/cxs/privacy/profiles/{profileID}/anonymize?scope=ASCOPE +curl -X POST http://localhost:8181/cxs/privacy/profiles/{profileID}/anonymize?scope=ASCOPE \ + --user "TENANT_ID:PRIVATE_KEY" ---- where `{profileID}` must be replaced by the actual identifier of a profile diff --git a/manual/src/main/asciidoc/property-types.adoc b/manual/src/main/asciidoc/property-types.adoc index cccc33b0a..dec203ad3 100644 --- a/manual/src/main/asciidoc/property-types.adoc +++ b/manual/src/main/asciidoc/property-types.adoc @@ -12,7 +12,7 @@ // limitations under the License. // -[[_property_types]] +[#_property_types] == Property Types Property types define the structure and metadata for properties that can be used in profiles and sessions within Apache Unomi. They specify the data type, display hints (ranges, constraints), default values, and other metadata that help inform UIs and enable rich querying capabilities. diff --git a/manual/src/main/asciidoc/queries-and-aggregations.adoc b/manual/src/main/asciidoc/queries-and-aggregations.adoc index aa536a03c..2f028f257 100644 --- a/manual/src/main/asciidoc/queries-and-aggregations.adoc +++ b/manual/src/main/asciidoc/queries-and-aggregations.adoc @@ -14,6 +14,8 @@ Apache Unomi contains a `query` endpoint that is quite powerful. It provides ways to perform queries that can quickly get result counts, apply metrics such as sum/min/max/avg or even use powerful aggregations. +Conditions used in queries follow the same JSON shape described in <<_conditions_guide,Conditions guide>>. + In this section we will show examples of requests that may be built using this API. === Query counts diff --git a/manual/src/main/asciidoc/recipes.adoc b/manual/src/main/asciidoc/recipes.adoc index 5d21c2d4d..8f0f8c9ce 100644 --- a/manual/src/main/asciidoc/recipes.adoc +++ b/manual/src/main/asciidoc/recipes.adoc @@ -18,7 +18,7 @@ In this section of the documentation we provide quick recipes focused on helping you achieve a specific result with Apache Unomi. -[[_enabling_debug_mode]] +[#_enabling_debug_mode] ==== Enabling debug mode Although the examples provided in this documentation are correct (they will work "as-is"), @@ -137,7 +137,7 @@ defining the corresponding JSON schema and you're ready to update profiles using only. Again, prefer the custom events solution because as this is a protected event it will require sending the Unomi key as a request header, and as Unomi only supports a single key for the moment it could be problematic if the key is intercepted. But at least by using an event you will get the benefits of auditing and historical property modification -tracing. +tracing (see <<_request_tracing_explain,request tracing>>). Let's go into more detail about the preferred way to update a profile. Let's consider the following example of a rule: diff --git a/manual/src/main/asciidoc/request-examples.adoc b/manual/src/main/asciidoc/request-examples.adoc index 69af6fe01..e8a8e6415 100644 --- a/manual/src/main/asciidoc/request-examples.adoc +++ b/manual/src/main/asciidoc/request-examples.adoc @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // -[[_request_examples]] +[#_request_examples] === Request examples ==== Prerequisites @@ -1382,10 +1382,13 @@ Best practices for Groovy actions: 5. Test with various date formats ==== +[#_request_tracing_explain] ==== Using the explain parameter for request tracing Apache Unomi provides a powerful request tracing feature through the `explain` query parameter. This feature helps administrators understand how requests are processed internally, including event processing, condition evaluations, and rule executions. +For save-time condition checks (before runtime), see <<_condition_validation,Condition validation>>. For a conditions-focused debugging walkthrough, see <<_debugging_conditions,Debugging conditions>>. + ===== Prerequisites To use the explain parameter, you must have one of the following roles: diff --git a/manual/src/main/asciidoc/samples/login-sample.adoc b/manual/src/main/asciidoc/samples/login-sample.adoc index ef04b18c8..30b7f1aee 100644 --- a/manual/src/main/asciidoc/samples/login-sample.adoc +++ b/manual/src/main/asciidoc/samples/login-sample.adoc @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // -[[_login_sample]] +[#_login_sample] === Login sample This samples is an example of what is involved in integrated a login with Apache Unomi. diff --git a/manual/src/main/asciidoc/samples/samples.adoc b/manual/src/main/asciidoc/samples/samples.adoc index 45d0c3829..a7f0db0b7 100644 --- a/manual/src/main/asciidoc/samples/samples.adoc +++ b/manual/src/main/asciidoc/samples/samples.adoc @@ -11,10 +11,12 @@ // See the License for the specific language governing permissions and // limitations under the License. // -[[_samples]] +[#_samples] === Samples -Apache Unomi provides the following samples: +Apache Unomi provides the following integration demos in `samples/`. +These are scenario walkthroughs, not maintained plugin templates — for plugin development see <<_writing_plugins,Writing plugins>>. + * <<_twitter_sample,Twitter integration>> * <<_login_sample,Login integration>> diff --git a/manual/src/main/asciidoc/samples/twitter-sample.adoc b/manual/src/main/asciidoc/samples/twitter-sample.adoc index 7ac2c65ec..a45955d90 100644 --- a/manual/src/main/asciidoc/samples/twitter-sample.adoc +++ b/manual/src/main/asciidoc/samples/twitter-sample.adoc @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // -[[_twitter_sample]] +[#_twitter_sample] === Twitter sample ==== Overview diff --git a/manual/src/main/asciidoc/scheduler.adoc b/manual/src/main/asciidoc/scheduler.adoc new file mode 100644 index 000000000..1f5976b42 --- /dev/null +++ b/manual/src/main/asciidoc/scheduler.adoc @@ -0,0 +1,695 @@ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +=== Cluster-aware task scheduler + +Apache Unomi 3.1 introduces a built-in **task scheduler** for background work that must run reliably in single-node and clustered deployments. + +Use it when you need to: + +* Run maintenance jobs on a timer (cache refresh, purge, segment recalculation). +* Offload heavy work from a request or rule action (for example profile merge follow-up). +* Coordinate work across cluster nodes with locks, recovery, and optional persistence. +* Inspect, retry, or cancel tasks through the REST API or Karaf shell. + +Tasks are identified by a **task type** string. Each type is handled by a registered `TaskExecutor` implementation (in core services or in a plugin). + +==== Quick example: periodic cache refresh + +Core services already use the scheduler for in-memory cache refresh. The pattern looks like this: + +[source,java] +---- +schedulerService.newTask("cache-refresh-segment") + .nonPersistent() // local to this node; not stored in OpenSearch/Elasticsearch + .withPeriod(1, TimeUnit.SECONDS) + .withFixedDelay() // wait for completion before next run + .disallowParallelExecution() // skip overlapping runs + .withSimpleExecutor(() -> refreshSegmentCache()) + .schedule(); +---- + +==== Architecture overview + +[plantuml] +---- +@startuml +skinparam componentStyle rectangle + +package "REST / Shell" { + [TaskEndpoint\n/cxs/tasks] as REST + [Karaf commands\nunomi:task-*] as Shell +} + +package "Scheduler API" { + [SchedulerService] as SchedAPI + [TaskExecutor] as ExecutorAPI + [ScheduledTask] as TaskModel +} + +package "Services (per node)" { + [SchedulerServiceImpl] as Impl + [TaskExecutionManager] as Exec + [TaskLockManager] as Lock + [TaskRecoveryManager] as Recovery + [TaskExecutorRegistry] as Registry +} + +database "Persistence\n(OpenSearch / ES)" as DB + +REST --> SchedAPI +Shell --> SchedAPI +SchedAPI --> Impl +Impl --> Exec +Impl --> Lock +Impl --> Recovery +Impl --> Registry +Registry --> ExecutorAPI +Impl --> DB : persistent tasks +Impl --> Impl : in-memory tasks + +note right of Impl + Only **executor nodes** pick up most tasks. + Tasks with runOnAllNodes=true + run on every node. +end note +@enduml +---- + +==== Task lifecycle + +A `ScheduledTask` moves through well-defined states. Operators usually see `SCHEDULED`, `RUNNING`, `COMPLETED`, `FAILED`, `CANCELLED`, or `CRASHED`. + +[plantuml] +---- +@startuml +[*] --> SCHEDULED : task created + +SCHEDULED --> WAITING : lock held or\ndependency not done +WAITING --> SCHEDULED : prerequisites met + +SCHEDULED --> RUNNING : executor node\nacquires lock +RUNNING --> COMPLETED : success +RUNNING --> FAILED : error (retries may apply) +RUNNING --> CRASHED : node failure + +FAILED --> SCHEDULED : manual retry\nor auto-retry +CRASHED --> RUNNING : resume from\ncheckpoint + +SCHEDULED --> CANCELLED : cancel/delete +RUNNING --> CANCELLED : cancel during run +COMPLETED --> [*] +CANCELLED --> [*] +@enduml +---- + +==== Execution flow in a cluster + +[plantuml] +---- +@startuml +participant "Node A\n(executor)" as A +participant "SchedulerService" as S +participant "TaskLockManager" as L +participant "Persistence" as P +participant "TaskExecutor" as E + +A -> S : check due tasks (every ~1s) +S -> P : load persistent tasks +S -> S : load in-memory tasks +S -> L : try acquire lock +alt lock acquired + L --> S : granted + S -> E : execute(task, callback) + E --> S : complete() / fail() + S -> P : save final state +else lock held by another node + L --> S : denied + S -> S : task stays WAITING or retries later +end +@enduml +---- + +=== Core concepts + +[cols="1,3",options="header"] +|=== +|Concept |Description + +|`taskType` +|Logical name of the work (for example `cache-refresh-segment`, `merge-profiles-reassign-data`). Must match a registered `TaskExecutor`. + +|`TaskExecutor` +|Java component that runs the work. Plugins register executors at activation time. + +|`persistent` task +|Stored in the persistence layer (`scheduledTask` items). Visible on all cluster nodes. Survives restarts. + +|In-memory task +|Exists only on the node that created it (`nonPersistent()`). Typical for local cache refresh. + +|Executor node +|A node configured with `scheduler.executorNode=true` that actually runs tasks. Non-executor nodes can still create and manage tasks. + +|`runOnAllNodes` +|When `true`, the task is eligible on every node (useful for per-node cache maintenance). + +|Lock +|Prevents duplicate execution of the same task (or task type when parallel execution is disallowed) across the cluster. + +|Checkpoint / resume +|Long tasks can save progress and be resumed after a `CRASHED` state via REST or shell. +|=== + +=== Task model + +Tasks are `ScheduledTask` items (`itemType = scheduledTask`). Important fields: + +[cols="1,3",options="header"] +|=== +|Field |Meaning + +|`taskType` |Executor lookup key +|`parameters` |JSON-serializable map passed to the executor +|`status` |`SCHEDULED`, `WAITING`, `RUNNING`, `COMPLETED`, `FAILED`, `CANCELLED`, `CRASHED` +|`persistent` |`true` = cluster-visible storage; `false` = node-local memory +|`oneShot` |Run once then stop +|`period` / `timeUnit` |Recurrence interval (0 means one-shot) +|`fixedRate` |`true` = interval from start; `false` = fixed delay from end +|`allowParallelExecution` |`false` = exclusive lock per task type +|`maxRetries` / `retryDelay` |Automatic retry policy after failure +|`dependsOn` |Task IDs that must complete first +|`checkpointData` / `currentStep` |Progress for resumable work +|`executingNodeId` / `lockOwner` |Cluster coordination metadata +|=== + +.Example task JSON (as returned by REST) +[source,json] +---- +{ + "itemId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "itemType": "scheduledTask", + "taskType": "merge-profiles-reassign-data", + "status": "COMPLETED", + "persistent": true, + "oneShot": true, + "allowParallelExecution": false, + "fixedRate": false, + "period": 0, + "timeUnit": "MILLISECONDS", + "initialDelay": 1000, + "failureCount": 0, + "successCount": 1, + "maxRetries": 3, + "retryDelay": 60000, + "parameters": { + "tenantId": "mytenant", + "masterProfileId": "profile-123", + "mergedProfileIds": ["profile-456", "profile-789"], + "anonymousBrowsing": false + }, + "lastExecutedBy": "unomi-node-1", + "lastExecutionDate": "2026-07-12T10:15:30.000Z" +} +---- + +=== Configuration + +Scheduler settings live in `etc/org.apache.unomi.services.cfg` (OSGi PID `org.apache.unomi.services`). + +[cols="2,2,4",options="header"] +|=== +|Property |Default |Description + +|`scheduler.thread.poolSize` |`5` |Thread pool size for task execution +|`scheduler.nodeId` |`test-scheduler-node` |Unique ID for this node in scheduler logs and task metadata. Set a stable value per node in production clusters. +|`scheduler.executorNode` |`true` |Whether this node runs tasks. Set `false` on dedicated REST-only nodes if you split roles. +|`scheduler.lockTimeout` |`10000` |Lock timeout in milliseconds +|`scheduler.purgeTaskEnabled` |`true` |Enables the built-in periodic purge of old completed tasks +|`task.purge.completedTaskTtlDays` |`30` |Age in days after which completed tasks may be purged +|=== + +.Example: dedicated executor node in a 3-node cluster + +[source,properties] +---- +# Node 1 and 2: REST + task execution +scheduler.executorNode=true +scheduler.nodeId=unomi-executor-1 + +# Node 3: API only, no task execution +scheduler.executorNode=false +scheduler.nodeId=unomi-api-3 +---- + +After changing configuration, restart the Unomi bundle or the Karaf container. + +=== REST API + +Base path: `{unomi}/cxs/tasks` + +Requires role `ROLE_UNOMI_ADMIN` or `ROLE_UNOMI_TENANT_ADMIN` (same as other administrative REST endpoints). + +NOTE: The REST API is for **monitoring and operations** (list, inspect, cancel, retry, resume). **Creating** tasks is done from Java code (`SchedulerService`) inside services or plugins, not via a public REST `POST /tasks` endpoint. + +==== List tasks + +.List all tasks (paginated) +[source,bash] +---- +curl -s -u karaf:karaf \ + "http://localhost:8181/cxs/tasks?offset=0&limit=20" +---- + +.Filter by status +[source,bash] +---- +curl -s -u karaf:karaf \ + "http://localhost:8181/cxs/tasks?status=FAILED&limit=50" +---- + +Valid status values: `SCHEDULED`, `WAITING`, `RUNNING`, `COMPLETED`, `FAILED`, `CANCELLED`, `CRASHED`. + +.Filter by task type +[source,bash] +---- +curl -s -u karaf:karaf \ + "http://localhost:8181/cxs/tasks?type=cache-refresh-segment&limit=10" +---- + +.Example response (truncated) +[source,json] +---- +{ + "list": [ + { + "itemId": "task-uuid-here", + "taskType": "cache-refresh-segment", + "status": "SCHEDULED", + "persistent": false, + "nextScheduledExecution": "2026-07-12T10:16:01.000Z" + } + ], + "offset": 0, + "pageSize": 20, + "totalSize": 42, + "totalSizeRelation": "EQUAL" +} +---- + +==== Get one task + +[source,bash] +---- +curl -s -u karaf:karaf \ + "http://localhost:8181/cxs/tasks/TASK_ID" +---- + +==== Cancel a task + +Cancellation stops future runs and marks the task `CANCELLED`. The task record remains in storage. + +[source,bash] +---- +curl -s -u karaf:karaf -X DELETE \ + "http://localhost:8181/cxs/tasks/TASK_ID" +---- + +Returns HTTP `204 No Content` on success. + +==== Retry a failed task + +[source,bash] +---- +# Retry keeping failure count +curl -s -u karaf:karaf -X POST \ + "http://localhost:8181/cxs/tasks/TASK_ID/retry" + +# Retry and reset failure count to zero +curl -s -u karaf:karaf -X POST \ + "http://localhost:8181/cxs/tasks/TASK_ID/retry?resetFailureCount=true" +---- + +==== Resume a crashed task + +Use when status is `CRASHED` and the executor supports checkpoint resume. + +[source,bash] +---- +curl -s -u karaf:karaf -X POST \ + "http://localhost:8181/cxs/tasks/TASK_ID/resume" +---- + +=== Karaf shell commands + +Connect to the shell (`ssh -p 8102 karaf@localhost`) and use the `unomi:task-*` commands. + +[cols="2,4",options="header"] +|=== +|Command |Description + +|`unomi:task-list` |List tasks. Options: `-s/--status`, `-t/--type`, `--limit` +|`unomi:task-show TASK_ID` |Detailed view including parameters and checkpoint data +|`unomi:task-cancel TASK_ID` |Cancel a task +|`unomi:task-retry TASK_ID` |Retry a failed task (`-r` resets failure count) +|`unomi:task-purge` |Delete old `COMPLETED` tasks (`-d` days, `-f` force) +|`unomi:task-executor` |Show whether this node is an executor node and its `nodeId` +|=== + +.Examples + +[source,shell] +---- +# List running tasks +unomi:task-list -s RUNNING + +# Show tasks created by profile merge follow-up +unomi:task-list -t merge-profiles-reassign-data + +# Inspect a single task +unomi:task-show a1b2c3d4-e5f6-7890-abcd-ef1234567890 + +# Retry after fixing upstream data +unomi:task-retry a1b2c3d4-e5f6-7890-abcd-ef1234567890 --reset + +# Purge completed tasks older than 14 days +unomi:task-purge --days 14 --force + +# Check executor configuration on this node +unomi:task-executor +---- + +=== Using the scheduler in plugins + +Plugins schedule work by: + +1. Injecting `SchedulerService` (OSGi reference). +2. Registering a `TaskExecutor` for a unique `taskType`. +3. Creating tasks with `newTask(...)`, `createRecurringTask(...)`, or `createTask(...)`. + +==== Pattern A: one-shot async work (real example) + +The `mergeProfilesOnProperty` action in `baseplugin` reassigns events and sessions in the background after a profile merge: + +[plantuml] +---- +@startuml +actor "Rule engine" as R +participant "MergeProfilesOnPropertyAction" as A +participant "SchedulerService" as S +participant "TaskExecutor\nmerge-profiles-reassign-data" as E +database "Persistence" as P + +R -> A : execute merge action +A -> S : registerTaskExecutor(...) +A -> S : newTask(...).asOneShot().schedule() +S -> P : save task (persistent) +S -> E : execute later on executor node +E -> P : update sessions/events\nin tenant context +E --> S : callback.complete() +@enduml +---- + +.Simplified plugin code + +[source,java] +---- +@Reference +private SchedulerService schedulerService; + +@Reference +private ExecutionContextManager executionContextManager; + +private static final String TASK_TYPE = "merge-profiles-reassign-data"; + +public void activate() { + schedulerService.registerTaskExecutor(new TaskExecutor() { + @Override + public String getTaskType() { + return TASK_TYPE; + } + + @Override + public void execute(ScheduledTask task, TaskStatusCallback callback) { + try { + String tenantId = (String) task.getParameters().get("tenantId"); + executionContextManager.executeAsTenant(tenantId, () -> { + // ... reassign sessions/events ... + return null; + }); + callback.complete(); + } catch (Exception e) { + callback.fail(e.getMessage()); + } + } + }); +} + +public void scheduleReassign(String tenantId, String masterId, List mergedIds) { + schedulerService.newTask(TASK_TYPE) + .withParameters(Map.of( + "tenantId", tenantId, + "masterProfileId", masterId, + "mergedProfileIds", mergedIds, + "anonymousBrowsing", false + )) + .withInitialDelay(1, TimeUnit.SECONDS) + .asOneShot() + .disallowParallelExecution() + .schedule(); +} +---- + +IMPORTANT: When task parameters include a `tenantId`, always run the work inside `executionContextManager.executeAsTenant(...)` (or set the security subject explicitly) so persistence queries respect tenant isolation. + +==== Pattern B: simple recurring maintenance + +For straightforward periodic code without a custom executor class: + +[source,java] +---- +schedulerService.createRecurringTask( + "my-plugin-daily-cleanup", + 24, TimeUnit.HOURS, + () -> cleanupStaleLocalCache(), + false // non-persistent: each node runs its own copy if executor +); +---- + +==== Pattern C: fluent builder (recommended) + +The builder covers most scheduling options in one place. + +.One-shot task with retries +[source,java] +---- +schedulerService.newTask("export-tenant-snapshot") + .withParameters(Map.of("tenantId", "mytenant", "destination", "/tmp/export")) + .withInitialDelay(5, TimeUnit.SECONDS) + .asOneShot() + .withMaxRetries(3) + .withRetryDelay(2, TimeUnit.MINUTES) + .disallowParallelExecution() + .withExecutor(myExportExecutor) + .schedule(); +---- + +.Periodic task with fixed delay +[source,java] +---- +schedulerService.newTask("refresh-external-lookup") + .withPeriod(15, TimeUnit.MINUTES) + .withFixedDelay() + .disallowParallelExecution() + .nonPersistent() + .withSimpleExecutor(() -> refreshLookupTable()) + .schedule(); +---- + +.Periodic task on every cluster node +[source,java] +---- +schedulerService.newTask("local-temp-file-cleanup") + .withPeriod(1, TimeUnit.HOURS) + .withFixedDelay() + .runOnAllNodes() + .nonPersistent() + .withSimpleExecutor(() -> deleteStaleTempFiles()) + .schedule(); +---- + +.Task chain with dependencies +[source,java] +---- +ScheduledTask extract = schedulerService.newTask("etl-extract") + .asOneShot() + .withExecutor(extractExecutor) + .schedule(); + +schedulerService.newTask("etl-load") + .asOneShot() + .withDependencies(extract.getItemId()) + .withExecutor(loadExecutor) + .schedule(); +---- + +==== Pattern D: resumable long-running executor + +For work that may run longer than a node failover window, save checkpoints and implement `canResume` / `resume`: + +[source,java] +---- +public class BulkReindexExecutor implements TaskExecutor { + + @Override + public String getTaskType() { + return "bulk-reindex"; + } + + @Override + public void execute(ScheduledTask task, TaskStatusCallback callback) { + runFromOffset(task, callback, 0); + } + + @Override + public boolean canResume(ScheduledTask task) { + return task.getCheckpointData() != null + && task.getCheckpointData().containsKey("lastOffset"); + } + + @Override + public void resume(ScheduledTask task, TaskStatusCallback callback) { + int offset = ((Number) task.getCheckpointData().get("lastOffset")).intValue(); + runFromOffset(task, callback, offset); + } + + private void runFromOffset(ScheduledTask task, TaskStatusCallback callback, int start) { + try { + for (int i = start; i < total; i += batchSize) { + reindexBatch(i, batchSize); + callback.updateStep("reindex", Map.of("lastOffset", i + batchSize)); + callback.checkpoint(Map.of("lastOffset", i + batchSize)); + } + callback.complete(); + } catch (Exception e) { + callback.fail(e.getMessage()); + } + } +} +---- + +After a crash, operators can call `POST /cxs/tasks/{id}/resume` or `unomi:task-retry` depending on the failure mode. + +==== OSGi Declarative Services wiring + +Inject `SchedulerService` with `@Reference` in your plugin component. +New plugins use Declarative Services — not Blueprint XML (see <<_writing_plugins,Writing plugins>>). + +[source,java] +---- +@Component(service = MyPluginService.class) +public class MyPluginService { + + private SchedulerService schedulerService; + private TaskExecutor registeredExecutor; + + @Reference + public void setSchedulerService(SchedulerService schedulerService) { + this.schedulerService = schedulerService; + } + + @Activate + public void activate() { + registeredExecutor = new MyTaskExecutor(); + schedulerService.registerTaskExecutor(registeredExecutor); + } + + @Deactivate + public void deactivate() { + if (schedulerService != null && registeredExecutor != null) { + schedulerService.unregisterTaskExecutor(registeredExecutor); + } + } +} +---- + +Register the executor in `activate()` and unregister in `deactivate()` when possible. +Pattern A above uses the same `@Reference` injection style for `SchedulerService`. + +=== Built-in tasks you will see in production + +Unomi core and plugins already create scheduler tasks. These are normal and expected: + +[cols="2,4",options="header"] +|=== +|Task type (examples) |Purpose + +|`cache-refresh-*` |Reload segments, rules, property types, scopes, etc. from persistence into memory +|`task-purge` |System task that purges old completed scheduler tasks +|`merge-profiles-reassign-data` |Async session/event reassignment after profile merge (`baseplugin`) +|=== + +Use `unomi:task-list` or `GET /cxs/tasks?type=...` to inspect them. + +=== Metrics + +`SchedulerService` exposes counters useful for monitoring: + +[source,java] +---- +Map metrics = schedulerService.getAllMetrics(); +// keys include: +// tasks.completed, tasks.failed, tasks.crashed, tasks.running, +// tasks.lock.conflicts, tasks.recovery.attempts, ... +---- + +In custom code: + +[source,java] +---- +long failures = schedulerService.getMetric("tasks.failed"); +---- + +=== Choosing persistence and execution settings + +[cols="1,1,1,2",options="header"] +|=== +|Need |persistent? |runOnAllNodes? |Notes + +|Cluster-wide job (ETL, merge follow-up) |yes |no |Survives restart; one executor runs it +|Per-node cache refresh |no |no or yes |`nonPersistent()` + often `runOnAllNodes()` for local caches +|Fire-and-forget after HTTP request |yes or no |no |One-shot; tune `maxRetries` +|Must never overlap |any |any |`disallowParallelExecution()` +|=== + +=== Troubleshooting + +[cols="1,3",options="header"] +|=== +|Symptom |What to check + +|Task stays `SCHEDULED` forever |Is this node an executor (`scheduler.executorNode=true`)? Is a `TaskExecutor` registered for the `taskType`? +|Task stays `WAITING` |Another run may hold the lock, or a dependency task is not `COMPLETED` +|Task `FAILED` after retries |Read `lastError` via `task-show` or REST; fix root cause; call retry +|Task `CRASHED` |Node failure during run; use resume if checkpoints exist +|Duplicate cache refresh tasks |Use `disallowParallelExecution()` and stable task names (see `AbstractMultiTypeCachingService`) +|Tenant data wrong in background job |Missing `executeAsTenant` / security subject setup in executor +|=== + +=== Related documentation + +* <<_request_examples,Tenant REST examples>> — creating tenants whose data background tasks will process +* <<_shell_commands,Karaf shell>> — operational commands including `unomi:task-*` +* <<_writing_plugins,Writing plugins>> — DS plugin registration and `META-INF/cxs/` layout +* <<_multitenancy,Multitenancy>> — tenant isolation rules diff --git a/manual/src/main/asciidoc/security.adoc b/manual/src/main/asciidoc/security.adoc index 02eb3bd83..a46996c62 100644 --- a/manual/src/main/asciidoc/security.adoc +++ b/manual/src/main/asciidoc/security.adoc @@ -79,4 +79,23 @@ end note @enduml ---- -===== Overview \ No newline at end of file +For tenant API keys, roles (`ROLE_UNOMI_TENANT_USER`, `ROLE_UNOMI_TENANT_ADMIN`, system administrator), and operation permissions, see <<_multitenancy,Multi-tenancy>>. + +===== Overview + +==== Authentication overview + +Unomi 3.1 uses a layered authentication model: + +* *Public endpoints* (for example `/cxs/context.json`, `/cxs/eventcollector`): require a tenant **public** API key via the `X-Unomi-Api-Key` header. +* *Private REST endpoints*: require tenant `tenantId:privateApiKey` basic authentication, or JAAS credentials with the `X-Unomi-Tenant-Id` header. +* *System administrator*: Karaf JAAS user (for example `karaf:karaf`) for tenant management and cluster operations. + +Configuration keys live in `etc/org.apache.unomi.rest.authentication.cfg` and `custom.system.properties`. Tenant API key settings are in `etc/org.apache.unomi.tenant.cfg`. + +For endpoint lists, auth examples, and migration from 2.x/3.0, see <<_multitenancy,Multi-tenancy>>, <<_configuration,Tenant management in Configuration>>, and <<_v2_compatibility_mode,V2 compatibility mode>>. + +==== Request tracing + +Administrators with the appropriate roles can enable request tracing with `explain=true` on context and event-collector requests. See <<_request_tracing_explain,Using the explain parameter for request tracing>>. + diff --git a/manual/src/main/asciidoc/shell-commands.adoc b/manual/src/main/asciidoc/shell-commands.adoc index b3c0e3946..801a82de8 100644 --- a/manual/src/main/asciidoc/shell-commands.adoc +++ b/manual/src/main/asciidoc/shell-commands.adoc @@ -11,6 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // +[#_shell_commands] === SSH Shell Commands Apache Unomi provides its own Apache Karaf Shell commands to make it easy to control the application @@ -204,3 +205,86 @@ working on a plugin. For example to remove all the definitions deployed by a plu command: `undeploy-definition BUNDLE_ID * *` when `BUNDLE_ID` is the identifier of the bundle that contains your plugin. |=== + + +==== Tenant commands + +Multi-tenant shell operations (Unomi 3.1+). See <<_multitenancy,Multi-tenancy>> for REST equivalents. + +.Table Tenant commands +|=== +|Command|Arguments|Description + +|tenant-get +|n/a +|Print the tenant ID stored for this Karaf session. + +|tenant-set +|tenant-id +|Set the current tenant for this shell session (`system` is allowed). Other CRUD commands use this context. + +|crud list tenant +|[--csv] +|List tenants (excludes the built-in `system` tenant from normal listings). + +|crud read tenant +|-i tenant-id +|Show one tenant as JSON. + +|crud create tenant +|-d JSON or --file path +|Create a tenant. Use `help` on the command for required properties. + +|crud update tenant +|-i tenant-id -d JSON or --file path +|Update tenant metadata. + +|crud delete tenant +|-i tenant-id +|Delete a tenant. + +|crud list apikey +|[--csv] +|List API keys across all tenants (keys are masked in the table). + +|crud create apikey +|-d JSON +|Create an API key for a tenant (`tenantId`, `keyType`, optional `validityPeriod`). + +|cache +|--tenant ID [--clear] +|Inspect or clear per-tenant caches (segments, rules, and related types). +|=== + +==== Scheduler commands + +These commands manage background tasks created by the cluster-aware scheduler (see <<_scheduler,Task scheduler>>). + +.Table Scheduler commands +|=== +|Command|Arguments|Description + +|task-list +|[-s status] [-t type] [--limit N] +|List scheduled tasks. Filter by status (`SCHEDULED`, `RUNNING`, `FAILED`, …) or task type. + +|task-show +|task-id +|Show full details for one task (parameters, checkpoint data, errors). + +|task-cancel +|task-id +|Cancel a task (stops future executions). + +|task-retry +|task-id [-r] +|Retry a failed task. `-r` / `--reset` clears the failure count. + +|task-purge +|[-d days] [-f] +|Delete old `COMPLETED` tasks (default: older than 7 days). `-f` skips confirmation. + +|task-executor +|boolean +|Show whether this node runs tasks and its scheduler `nodeId`. +|=== diff --git a/manual/src/main/asciidoc/unomi-manual.css b/manual/src/main/asciidoc/unomi-manual.css new file mode 100644 index 000000000..21be86fbb --- /dev/null +++ b/manual/src/main/asciidoc/unomi-manual.css @@ -0,0 +1,3241 @@ + +@import "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap"; + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +nav, +section, +summary { + display: block +} + +audio, +canvas, +video { + display: inline-block +} + +audio:not([controls]) { + display: none; + height: 0 +} + +script { + display: none !important +} + +html { + font-family: "Droid Serif"; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; +} + +a { + background: transparent +} + +a:focus { + outline: thin dotted +} + +a:active, +a:hover { + outline: 0 +} + +h1 { + font-size: 2em; + margin: .67em 0 +} + +abbr[title] { + border-bottom: 1px dotted +} + +b, +strong { + font-weight: bold +} + +dfn { + font-style: italic +} + +hr { + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0 +} + +mark { + background: #ff0; + color: #000 +} + +code, +kbd, +pre, +samp { + font-family: monospace; + font-size: 1em +} + +pre { + white-space: pre-wrap +} + +q { + quotes: "\201C" "\201D" "\2018" "\2019" +} + +small { + font-size: 80% +} + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline +} + +sup { + top: -.5em +} + +sub { + bottom: -.25em +} + +img { + border: 0 +} + +svg:not(:root) { + overflow: hidden +} + +figure { + margin: 0 +} + +fieldset { + border: 1px solid silver; + margin: 0 2px; + padding: .35em .625em .75em +} + +legend { + border: 0; + padding: 0 +} + +button, +input, +select, +textarea { + font-family: inherit; + font-size: 100%; + margin: 0 +} + +button, +input { + line-height: normal +} + +button, +select { + text-transform: none +} + +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; + cursor: pointer +} + +button[disabled], +html input[disabled] { + cursor: default +} + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; + padding: 0 +} + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0 +} + +textarea { + overflow: auto; + vertical-align: top +} + +table { + border-collapse: collapse; + border-spacing: 0 +} + +*, +*::before, +*::after { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box +} + +html, +body { + /*font-size: 100%*/ +} + +body { + background: #fff; + color: #333; + padding: 0; + margin: 0; + font-family: "Droid Serif", "DejaVu Serif", serif; + font-size: 14px; + font-style: normal; + line-height: 1.42857143; + position: relative; + cursor: auto; + tab-size: 4; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased +} + +a:hover { + cursor: pointer +} + +img, +object, +embed { + max-width: 100%; + height: auto +} + +object, +embed { + height: 100% +} + +img { + -ms-interpolation-mode: bicubic +} + +.left { + float: left !important +} + +.right { + float: right !important +} + +.text-left { + text-align: left !important +} + +.text-right { + text-align: right !important +} + +.text-center { + text-align: center !important +} + +.text-justify { + text-align: justify !important +} + +.hide { + display: none +} + +img, +object, +svg { + display: inline-block; + vertical-align: middle +} + +textarea { + height: auto; + min-height: 50px +} + +select { + width: 100% +} + +.center { + margin-left: auto; + margin-right: auto +} + +.stretch { + width: 100% +} + +.subheader, +.admonitionblock td.content>.title, +.audioblock>.title, +.exampleblock>.title, +.imageblock>.title, +.listingblock>.title, +.literalblock>.title, +.stemblock>.title, +.openblock>.title, +.paragraph>.title, +.quoteblock>.title, +table.tableblock>.title, +.verseblock>.title, +.videoblock>.title, +.dlist>.title, +.olist>.title, +.ulist>.title, +.qlist>.title, +.hdlist>.title { + line-height: 1.45; + color: #6754d1; + font-weight: 400; + margin-top: 0; + margin-bottom: .25em +} + +div, +dl, +dt, +dd, +ul, +ol, +li, +h1, +h2, +h3, +#toctitle, +.sidebarblock>.content>.title, +h4, +h5, +h6, +pre, +form, +p, +blockquote, +th, +td { + margin: 0; + padding: 0; + direction: ltr +} + +a { + color: #6754d1; + text-decoration: underline; + line-height: inherit +} + +a:hover, +a:focus { + color: #5243b8; +} + +a img { + border: none +} + +p { + font-family: inherit; + font-weight: 400; + font-size: 1em; + line-height: 1.6; + margin-bottom: 1.25em; + text-rendering: optimizeLegibility +} + +p aside { + font-size: .875em; + line-height: 1.35; + font-style: italic +} + +h1, +h2, +h3, +#toctitle, +.sidebarblock>.content>.title, +h4, +h5, +h6 { + font-family: "Open Sans", "DejaVu Sans", sans-serif; + font-weight: bold; + /*font-style: bold;*/ + color: #3d3489; + text-rendering: optimizeLegibility; + margin-top: 1em; + margin-bottom: .5em; + line-height: 1.0125em; + text-transform: none; +} + +h1 small, +h2 small, +h3 small, +#toctitle small, +.sidebarblock>.content>.title small, +h4 small, +h5 small, +h6 small { + font-size: 60%; + color: #3d3489; + line-height: 0 +} + +h1 { + font-size: 2.125em +} + +h2 { + font-size: 1.6875em +} + +h3, +#toctitle, +.sidebarblock>.content>.title { + font-size: 1.375em +} + +h4, +h5 { + font-size: 1.125em +} + +h6 { + font-size: 1em +} + +hr { + border: solid #dddddd; + border-width: 1px 0 0; + clear: both; + margin: 1.25em 0 1.1875em; + height: 0 +} + +em, +i { + font-style: italic; + line-height: inherit +} + +strong, +b { + font-weight: bold; + line-height: inherit +} + +small { + font-size: 60%; + line-height: inherit +} + +code { + font-family: "Droid Sans Mono", "DejaVu Sans Mono", monospace; + font-weight: 400; + color: #6754d1; +} + +ul, +ol, +dl { + font-size: 1em; + line-height: 1.6; + margin-bottom: 1.25em; + list-style-position: outside; + font-family: inherit +} + +ul, +ol { + margin-left: 1.5em +} + +ul li ul, +ul li ol { + margin-left: 1.25em; + margin-bottom: 0; + font-size: 1em +} + +ul.square li ul, +ul.circle li ul, +ul.disc li ul { + list-style: inherit +} + +ul.square { + list-style-type: square +} + +ul.circle { + list-style-type: circle +} + +ul.disc { + list-style-type: disc +} + +ol li ul, +ol li ol { + margin-left: 1.25em; + margin-bottom: 0 +} + +dl dt { + margin-bottom: .3125em; + font-weight: bold +} + +dl dd { + margin-bottom: 1.25em +} + +abbr, +acronym { + text-transform: uppercase; + font-size: 90%; + color: rgba(0, 0, 0, .8); + border-bottom: 1px dotted #ddd; + cursor: help +} + +abbr { + text-transform: none +} + +blockquote { + margin: 0 0 1.25em; + padding: .5625em 1.25em 0 1.1875em; + border-left: 1px solid #ddd +} + +blockquote cite { + display: block; + font-size: .9375em; + color: rgba(0, 0, 0, .6) +} + +blockquote cite::before { + content: "\2014 \0020" +} + +blockquote cite a, +blockquote cite a:visited { + color: rgba(0, 0, 0, .6) +} + +blockquote, +blockquote p { + line-height: 1.6; + color: rgba(0, 0, 0, .85) +} + +@media screen and (min-width:768px) { + h1, + h2, + h3, + #toctitle, + .sidebarblock>.content>.title, + h4, + h5, + h6 { + line-height: 1.2 + } + + h1 { + font-size: 2.75em + } + + h2 { + font-size: 2.3125em + } + + h3, + #toctitle, + .sidebarblock>.content>.title { + font-size: 1.6875em + } + + h4 { + font-size: 1.4375em + } + +} + +table { + background: #fff; + margin-bottom: 1.25em; + border: solid 1px #dddddd; +} + +table thead, +table tfoot { + background: #f7f8f7 +} + +table thead tr th, +table thead tr td, +table tfoot tr th, +table tfoot tr td { + padding: .5em .625em .625em; + font-size: inherit; + color: rgba(0, 0, 0, .8); + text-align: left +} + +table tr th, +table tr td { + padding: .5625em .625em; + font-size: inherit; + color: rgba(0, 0, 0, .8) +} + +table tr.even, +table tr.alt, +table tr:nth-of-type(even) { + background: #f8f8f7 +} + +table thead tr th, +table tfoot tr th, +table tbody tr td, +table tr td, +table tfoot tr td { + display: table-cell; + line-height: 1.6 +} + +h1, +h2, +h3, +#toctitle, +.sidebarblock>.content>.title, +h4, +h5, +h6 { + line-height: 1.2; + word-spacing: -.05em +} + +h1 strong, +h2 strong, +h3 strong, +#toctitle strong, +.sidebarblock>.content>.title strong, +h4 strong, +h5 strong, +h6 strong { + font-weight: 400 +} + +.clearfix::before, +.clearfix::after, +.float-group::before, +.float-group::after { + content: " "; + display: table +} + +.clearfix::after, +.float-group::after { + clear: both +} + +*:not(pre)>code { + font-size: .9375em; + font-style: normal !important; + letter-spacing: 0; + padding: .1em .5ex; + word-spacing: -.15em; + background-color: #f7f7f8; + -webkit-border-radius: 4px; + border-radius: 4px; + line-height: 1.45; + text-rendering: optimizeSpeed; + word-wrap: break-word +} + +*:not(pre)>code.nobreak { + word-wrap: normal +} + +*:not(pre)>code.nowrap { + white-space: nowrap +} + +pre, +pre>code { + line-height: 1.45; + color: #6754d1; + font-family: "Droid Sans Mono", "DejaVu Sans Mono", monospace; + font-weight: 400; + text-rendering: optimizeSpeed +} + +em em { + font-style: normal +} + +strong strong { + font-weight: 400 +} + +.keyseq { + color: rgba(51, 51, 51, .8) +} + +kbd { + font-family: "Droid Sans Mono", "DejaVu Sans Mono", monospace; + display: inline-block; + color: rgba(0, 0, 0, .8); + font-size: .65em; + line-height: 1.45; + background-color: #f7f7f7; + border: 1px solid #dddddd; + -webkit-border-radius: 3px; + border-radius: 3px; + -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, .2), 0 0 0 .1em white inset; + box-shadow: 0 1px 0 rgba(0, 0, 0, .2), 0 0 0 .1em #fff inset; + margin: 0 .15em; + padding: .2em .5em; + vertical-align: middle; + position: relative; + top: -.1em; + white-space: nowrap +} + +.keyseq kbd:first-child { + margin-left: 0 +} + +.keyseq kbd:last-child { + margin-right: 0 +} + +.menuseq, +.menuref { + color: #000 +} + +.menuseq b:not(.caret),.menuref { + font-weight: inherit +} + +.menuseq { + word-spacing: -.02em +} + +.menuseq b.caret { + font-size: 1.25em; + line-height: .8 +} + +.menuseq i.caret { + font-weight: bold; + text-align: center; + width: .45em +} + +b.button::before, +b.button::after { + position: relative; + top: -1px; + font-weight: 400 +} + +b.button::before { + content: "["; + padding: 0 3px 0 2px +} + +b.button::after { + content: "]"; + padding: 0 2px 0 3px +} + +p a>code:hover { + color: #6754d1; +} + +#header, +#content, +#footnotes, +#footer { + width: 100%; + margin-left: auto; + margin-right: auto; + margin-top: 0; + margin-bottom: 0; + max-width: 62.5em; + *zoom: 1; + position: relative; + padding-left: .9375em; + padding-right: .9375em +} + +#header::before, +#header::after, +#content::before, +#content::after, +#footnotes::before, +#footnotes::after, +#footer::before, +#footer::after { + content: " "; + display: table +} + +#header::after, +#content::after, +#footnotes::after, +#footer::after { + clear: both +} + +#content { + margin-top: 1.25em +} + +#content::before { + content: none +} + +#header>h1:first-child { + color: #3d3489; + margin-top: 2.25rem; + margin-bottom: 0 +} + +#header>h1:first-child+#toc { + margin-top: 8px; + border-top: 1px solid #dddddd +} + +#header>h1:only-child, +body.toc2 #header>h1:nth-last-child(2) { + border-bottom: 1px solid #dddddd; + padding-bottom: 8px +} + +#header .details { + border-bottom: 1px solid #dddddd; + line-height: 1.45; + padding-top: .25em; + padding-bottom: .25em; + padding-left: .25em; + color: rgba(0, 0, 0, .6); + display: -ms-flexbox; + display: -webkit-flex; + display: flex; + -ms-flex-flow: row wrap; + -webkit-flex-flow: row wrap; + flex-flow: row wrap +} + +#header .details span:first-child { + margin-left: -.125em +} + +#header .details span.email a { + color: rgba(0, 0, 0, .85) +} + +#header .details br { + display: none +} + +#header .details br+span::before { + content: "\00a0\2013\00a0" +} + +#header .details br+span.author::before { + content: "\00a0\22c5\00a0"; + color: rgba(0, 0, 0, .85) +} + +#header .details br+span#revremark::before { + content: "\00a0|\00a0" +} + +#header #revnumber { + text-transform: capitalize +} + +#header #revnumber::after { + content: "\00a0" +} + +#content>h1:first-child:not([class]) { + color: rgba(0, 0, 0, .85); + border-bottom: 1px solid #dddddd +; + padding-bottom: 8px; + margin-top: 0; + padding-top: 1rem; + margin-bottom: 1.25rem +} + +#toc { + border-bottom: 1px solid #dddddd; + padding-bottom: .5em +} + +#toc>ul { + margin-left: .125em +} + +#toc ul.sectlevel0>li>a { + font-style: italic +} + +#toc ul.sectlevel0 ul.sectlevel1 { + margin: .5em 0 +} + +#toc ul { + font-family: "Droid Serif", "DejaVu Sans", sans-serif; + list-style-type: none +} + +#toc li { + line-height: 1.3334; + margin-top: .3334em +} + +#toc a { + text-decoration: none +} + +#toc a:active { + text-decoration: underline +} + +#toctitle { + color: #3d3489; + font-size: 1.2em +} + +@media screen and (min-width:768px) { + #toctitle { + font-size: 1.375em + } + + body.toc2 { + padding-left: 15em; + padding-right: 0 + } + + #toc.toc2 { + margin-top: 0 !important; + background-color: #eee; + position: fixed; + width: 15em; + left: 0; + top: 0; + border-right: 1px solid #dddddd; + border-top-width: 0 !important; + border-bottom-width: 0 !important; + z-index: 1000; + padding: 1.25em 1em; + height: 100%; + overflow: auto + } + + #toc.toc2 #toctitle { + margin-top: 0; + margin-bottom: .8rem; + font-size: 1.2em + } + + #toc.toc2>ul { + font-size: .9em; + margin-bottom: 0 + } + + #toc.toc2 ul ul { + margin-left: 0; + padding-left: 1em + } + + #toc.toc2 ul.sectlevel0 ul.sectlevel1 { + padding-left: 0; + margin-top: .5em; + margin-bottom: .5em + } + + body.toc2.toc-right { + padding-left: 0; + padding-right: 15em + } + + body.toc2.toc-right #toc.toc2 { + border-right-width: 0; + border-left: 1px solid #dddddd; + left: auto; + right: 0 + } + +} + +@media screen and (min-width:1280px) { + body.toc2 { + padding-left: 20em; + padding-right: 0 + } + + #toc.toc2 { + width: 20em + } + + #toc.toc2 #toctitle { + font-size: 1.375em + } + + #toc.toc2>ul { + font-size: .95em + } + + #toc.toc2 ul ul { + padding-left: 1.25em + } + + body.toc2.toc-right { + padding-left: 0; + padding-right: 20em + } + +} + +#content #toc { + border-style: solid; + border-width: 1px; + border-color: #dddddd; + margin-bottom: 1.25em; + padding: 1.25em; + background: #f8f8f7; + -webkit-border-radius: 4px; + border-radius: 4px +} + +#content #toc>:first-child { + margin-top: 0 +} + +#content #toc>:last-child { + margin-bottom: 0 +} + +#footer { + max-width: 100%; + background-color: #3d3489; + padding: 1.25em +} + +#footer-text { + color: #fff; + line-height: 1.44 +} + +#content { + margin-bottom: .625em +} + +.sect1 { + padding-bottom: .625em +} + +@media screen and (min-width:768px) { + #content { + margin-bottom: 1.25em + } + + .sect1 { + padding-bottom: 1.25em + } + +} + +.sect1:last-child { + padding-bottom: 0 +} + +.sect1+.sect1 { + border-top: 1px solid #dddddd; +} + +#content h1>a.anchor, +h2>a.anchor, +h3>a.anchor, +#toctitle>a.anchor, +.sidebarblock>.content>.title>a.anchor, +h4>a.anchor, +h5>a.anchor, +h6>a.anchor { + position: absolute; + z-index: 1001; + width: 1.5ex; + margin-left: -1.5ex; + display: block; + text-decoration: none !important; + visibility: hidden; + text-align: center; + font-weight: 400 +} + +#content h1>a.anchor::before, +h2>a.anchor::before, +h3>a.anchor::before, +#toctitle>a.anchor::before, +.sidebarblock>.content>.title>a.anchor::before, +h4>a.anchor::before, +h5>a.anchor::before, +h6>a.anchor::before { + content: "\00A7"; + font-size: .85em; + display: block; + padding-top: .1em +} + +#content h1:hover>a.anchor, +#content h1>a.anchor:hover, +h2:hover>a.anchor, +h2>a.anchor:hover, +h3:hover>a.anchor, +#toctitle:hover>a.anchor, +.sidebarblock>.content>.title:hover>a.anchor, +h3>a.anchor:hover, +#toctitle>a.anchor:hover, +.sidebarblock>.content>.title>a.anchor:hover, +h4:hover>a.anchor, +h4>a.anchor:hover, +h5:hover>a.anchor, +h5>a.anchor:hover, +h6:hover>a.anchor, +h6>a.anchor:hover { + visibility: visible +} + +#content h1>a.link, +h2>a.link, +h3>a.link, +#toctitle>a.link, +.sidebarblock>.content>.title>a.link, +h4>a.link, +h5>a.link, +h6>a.link { + color: #ba3925; + text-decoration: none +} + +#content h1>a.link:hover, +h2>a.link:hover, +h3>a.link:hover, +#toctitle>a.link:hover, +.sidebarblock>.content>.title>a.link:hover, +h4>a.link:hover, +h5>a.link:hover, +h6>a.link:hover { + color: #a53221 +} + +.audioblock, +.imageblock, +.literalblock, +.listingblock, +.stemblock, +.videoblock { + margin-bottom: 1.25em +} + +.admonitionblock td.content>.title, +.audioblock>.title, +.exampleblock>.title, +.imageblock>.title, +.listingblock>.title, +.literalblock>.title, +.stemblock>.title, +.openblock>.title, +.paragraph>.title, +.quoteblock>.title, +table.tableblock>.title, +.verseblock>.title, +.videoblock>.title, +.dlist>.title, +.olist>.title, +.ulist>.title, +.qlist>.title, +.hdlist>.title { + text-rendering: optimizeLegibility; + text-align: left; + font-family: "Droid Serif", "DejaVu Serif", serif; + font-size: 1rem; + font-style: italic +} + +table.tableblock.fit-content>caption.title { + white-space: nowrap; + width: 0 +} + +.paragraph.lead>p, +#preamble>.sectionbody>[class="paragraph"]:first-of-type p { + font-size: 1.21875em; + line-height: 1.6; + color: rgba(0, 0, 0, .85) +} + +table.tableblock #preamble>.sectionbody>[class="paragraph"]:first-of-type p { + font-size: inherit +} + +.admonitionblock>table { + border-collapse: separate; + border: 0; + background: none; + width: 100% +} + +.admonitionblock>table td.icon { + text-align: center; + width: 80px +} + +.admonitionblock>table td.icon img { + max-width: none +} + +.admonitionblock>table td.icon .title { + font-weight: bold; + font-family: "Open Sans", "DejaVu Sans", sans-serif; + text-transform: uppercase +} + +.admonitionblock>table td.content { + padding-left: 1.125em; + padding-right: 1.25em; + border-left: 1px solid #dddddd; + color: rgba(0, 0, 0, .6) +} + +.admonitionblock>table td.content>:last-child>:last-child { + margin-bottom: 0 +} + +.exampleblock>.content { + border-style: solid; + border-width: 1px; + border-color: #dddddd; + margin-bottom: 1.25em; + padding: 1.25em; + background: #fff; + -webkit-border-radius: 4px; + border-radius: 4px +} + +.exampleblock>.content>:first-child { + margin-top: 0 +} + +.exampleblock>.content>:last-child { + margin-bottom: 0 +} + +.sidebarblock { + border-style: solid; + border-width: 1px; + border-color: #dddddd; + margin-bottom: 1.25em; + padding: 1.25em; + background: #f8f8f7; + -webkit-border-radius: 4px; + border-radius: 4px +} + +.sidebarblock>:first-child { + margin-top: 0 +} + +.sidebarblock>:last-child { + margin-bottom: 0 +} + +.sidebarblock>.content>.title { + color: #3d3489; + margin-top: 0; + text-align: center +} + +.exampleblock>.content>:last-child>:last-child, +.exampleblock>.content .olist>ol>li:last-child>:last-child, +.exampleblock>.content .ulist>ul>li:last-child>:last-child, +.exampleblock>.content .qlist>ol>li:last-child>:last-child, +.sidebarblock>.content>:last-child>:last-child, +.sidebarblock>.content .olist>ol>li:last-child>:last-child, +.sidebarblock>.content .ulist>ul>li:last-child>:last-child, +.sidebarblock>.content .qlist>ol>li:last-child>:last-child { + margin-bottom: 0 +} + +.literalblock pre, +.listingblock pre:not(.highlight),.listingblock pre[class="highlight"],.listingblock pre[class^="highlight "],.listingblock pre.CodeRay,.listingblock pre.prettyprint { + background: #f7f7f8 +} + +.sidebarblock .literalblock pre, +.sidebarblock .listingblock pre:not(.highlight),.sidebarblock .listingblock pre[class="highlight"],.sidebarblock .listingblock pre[class^="highlight "],.sidebarblock .listingblock pre.CodeRay,.sidebarblock .listingblock pre.prettyprint { + background: #f2f1f1 +} + +.literalblock pre, +.literalblock pre[class], +.listingblock pre, +.listingblock pre[class] { + -webkit-border-radius: 4px; + border-radius: 4px; + word-wrap: break-word; + padding: 1em; + font-size: .8125em +} + +.literalblock pre.nowrap, +.literalblock pre[class].nowrap, +.listingblock pre.nowrap, +.listingblock pre[class].nowrap { + overflow-x: auto; + white-space: pre; + word-wrap: normal +} + +@media screen and (min-width:768px) { + .literalblock pre, + .literalblock pre[class], + .listingblock pre, + .listingblock pre[class] { + font-size: .90625em + } + +} + +@media screen and (min-width:1280px) { + .literalblock pre, + .literalblock pre[class], + .listingblock pre, + .listingblock pre[class] { + font-size: 1em + } + +} + +.literalblock.output pre { + color: #f7f7f8; + background-color: rgba(0, 0, 0, .9) +} + +.listingblock pre.highlightjs { + padding: 0 +} + +.listingblock pre.highlightjs>code { + padding: 1em; + -webkit-border-radius: 4px; + border-radius: 4px +} + +.listingblock pre.prettyprint { + border-width: 0 +} + +.listingblock>.content { + position: relative +} + +.listingblock code[data-lang]::before { + display: none; + content: attr(data-lang); + position: absolute; + font-size: .75em; + top: .425rem; + right: .5rem; + line-height: 1; + text-transform: uppercase; + color: #999 +} + +.listingblock:hover code[data-lang]::before { + display: block +} + +.listingblock.terminal pre .command::before { + content: attr(data-prompt); + padding-right: .5em; + color: #999 +} + +.listingblock.terminal pre .command:not([data-prompt])::before { + content: "$" +} + +table.pyhltable { + border-collapse: separate; + border: 0; + margin-bottom: 0; + background: none +} + +table.pyhltable td { + vertical-align: top; + padding-top: 0; + padding-bottom: 0; + line-height: 1.45 +} + +table.pyhltable td.code { + padding-left: .75em; + padding-right: 0 +} + +pre.pygments .lineno, +table.pyhltable td:not(.code) { + color: #999; + padding-left: 0; + padding-right: .5em; + border-right: 1px solid #dddddd; +} + +pre.pygments .lineno { + display: inline-block; + margin-right: .25em +} + +table.pyhltable .linenodiv { + background: none !important; + padding-right: 0 !important +} + +.quoteblock { + margin: 0 1em 1.25em 1.5em; + display: table +} + +.quoteblock>.title { + margin-left: -1.5em; + margin-bottom: .75em +} + +.quoteblock blockquote, +.quoteblock blockquote p { + color: rgba(0, 0, 0, .85); + font-size: 1.05rem; + line-height: 1.75; + word-spacing: .1em; + letter-spacing: 0; + font-style: italic; + text-align: justify +} + +.quoteblock blockquote { + margin: 0; + padding: 0; + border: 0 +} + +.quoteblock blockquote::before { + content: "\201c"; + float: left; + font-size: 2.75em; + font-weight: bold; + line-height: .6em; + margin-left: -.6em; + color: #3d3489; + text-shadow: 0 1px 2px rgba(0, 0, 0, .1) +} + +.quoteblock blockquote>.paragraph:last-child p { + margin-bottom: 0 +} + +.quoteblock .attribution { + margin-top: .5em; + margin-right: .5ex; + text-align: right +} + +.quoteblock .quoteblock { + margin-left: 0; + margin-right: 0; + padding: .5em 0; + border-left: 3px solid rgba(0, 0, 0, .6) +} + +.quoteblock .quoteblock blockquote { + padding: 0 0 0 .75em +} + +.quoteblock .quoteblock blockquote::before { + display: none +} + +.verseblock { + margin: 0 1em 1.25em +} + +.verseblock pre { + font-family: "Open Sans", "DejaVu Sans", sans; + font-size: 1.15rem; + color: rgba(0, 0, 0, .85); + font-weight: 300; + text-rendering: optimizeLegibility +} + +.verseblock pre strong { + font-weight: 400 +} + +.verseblock .attribution { + margin-top: 1.25rem; + margin-left: .5ex +} + +.quoteblock .attribution, +.verseblock .attribution { + font-size: .9375em; + line-height: 1.45; + font-style: italic +} + +.quoteblock .attribution br, +.verseblock .attribution br { + display: none +} + +.quoteblock .attribution cite, +.verseblock .attribution cite { + display: block; + letter-spacing: -.025em; + color: rgba(0, 0, 0, .6) +} + +.quoteblock.abstract { + margin: 0 1em 1.25em; + display: block +} + +.quoteblock.abstract>.title { + margin: 0 0 .375em; + font-size: 1.15em; + text-align: center +} + +.quoteblock.abstract blockquote, +.quoteblock.abstract blockquote p { + word-spacing: 0; + line-height: 1.6 +} + +.quoteblock.abstract blockquote::before, +.quoteblock.abstract p::before { + display: none +} + +table.tableblock { + max-width: 100%; + border-collapse: separate +} + +p.tableblock:last-child { + margin-bottom: 0 +} + +td.tableblock>.content { + margin-bottom: -1.25em +} + +table.tableblock, +th.tableblock, +td.tableblock { + border: 0 solid #dddddd; +} + +table.grid-all>thead>tr>.tableblock, +table.grid-all>tbody>tr>.tableblock { + border-width: 0 1px 1px 0 +} + +table.grid-all>tfoot>tr>.tableblock { + border-width: 1px 1px 0 0 +} + +table.grid-cols>*>tr>.tableblock { + border-width: 0 1px 0 0 +} + +table.grid-rows>thead>tr>.tableblock, +table.grid-rows>tbody>tr>.tableblock { + border-width: 0 0 1px +} + +table.grid-rows>tfoot>tr>.tableblock { + border-width: 1px 0 0 +} + +table.grid-all>*>tr>.tableblock:last-child, +table.grid-cols>*>tr>.tableblock:last-child { + border-right-width: 0 +} + +table.grid-all>tbody>tr:last-child>.tableblock, +table.grid-all>thead:last-child>tr>.tableblock, +table.grid-rows>tbody>tr:last-child>.tableblock, +table.grid-rows>thead:last-child>tr>.tableblock { + border-bottom-width: 0 +} + +table.frame-all { + border-width: 1px +} + +table.frame-sides { + border-width: 0 1px +} + +table.frame-topbot, +table.frame-ends { + border-width: 1px 0 +} + +table.stripes-all tr, +table.stripes-odd tr:nth-of-type(odd) { + background: #f8f8f7 +} + +table.stripes-none tr, +table.stripes-odd tr:nth-of-type(even) { + background: none +} + +th.halign-left, +td.halign-left { + text-align: left +} + +th.halign-right, +td.halign-right { + text-align: right +} + +th.halign-center, +td.halign-center { + text-align: center +} + +th.valign-top, +td.valign-top { + vertical-align: top +} + +th.valign-bottom, +td.valign-bottom { + vertical-align: bottom +} + +th.valign-middle, +td.valign-middle { + vertical-align: middle +} + +table thead th, +table tfoot th { + font-weight: bold +} + +tbody tr th { + display: table-cell; + line-height: 1.6; + background: #f7f8f7 +} + +tbody tr th, +tbody tr th p, +tfoot tr th, +tfoot tr th p { + color: rgba(0, 0, 0, .8); + font-weight: bold +} + +p.tableblock>code:only-child { + background: none; + padding: 0 +} + +p.tableblock { + font-size: 1em +} + +td>div.verse { + white-space: pre +} + +ol { + margin-left: 1.75em +} + +ul li ol { + margin-left: 1.5em +} + +dl dd { + margin-left: 1.125em +} + +dl dd:last-child, +dl dd:last-child>:last-child { + margin-bottom: 0 +} + +ol>li p, +ul>li p, +ul dd, +ol dd, +.olist .olist, +.ulist .ulist, +.ulist .olist, +.olist .ulist { + margin-bottom: .625em +} + +ul.checklist, +ul.none, +ol.none, +ul.no-bullet, +ol.no-bullet, +ol.unnumbered, +ul.unstyled, +ol.unstyled { + list-style-type: none +} + +ul.no-bullet, +ol.no-bullet, +ol.unnumbered { + margin-left: .625em +} + +ul.unstyled, +ol.unstyled { + margin-left: 0 +} + +ul.checklist { + margin-left: .625em +} + +ul.checklist li>p:first-child>.fa-square-o:first-child, +ul.checklist li>p:first-child>.fa-check-square-o:first-child { + width: 1.25em; + font-size: .8em; + position: relative; + bottom: .125em +} + +ul.checklist li>p:first-child>input[type="checkbox"]:first-child { + margin-right: .25em +} + +ul.inline { + display: -ms-flexbox; + display: -webkit-box; + display: flex; + -ms-flex-flow: row wrap; + -webkit-flex-flow: row wrap; + flex-flow: row wrap; + list-style: none; + margin: 0 0 .625em -1.25em +} + +ul.inline>li { + margin-left: 1.25em +} + +.unstyled dl dt { + font-weight: 400; + font-style: normal +} + +ol.arabic { + list-style-type: decimal +} + +ol.decimal { + list-style-type: decimal-leading-zero +} + +ol.loweralpha { + list-style-type: lower-alpha +} + +ol.upperalpha { + list-style-type: upper-alpha +} + +ol.lowerroman { + list-style-type: lower-roman +} + +ol.upperroman { + list-style-type: upper-roman +} + +ol.lowergreek { + list-style-type: lower-greek +} + +.hdlist>table, +.colist>table { + border: 0; + background: none +} + +.hdlist>table>tbody>tr, +.colist>table>tbody>tr { + background: none +} + +td.hdlist1, +td.hdlist2 { + vertical-align: top; + padding: 0 .625em +} + +td.hdlist1 { + font-weight: bold; + padding-bottom: 1.25em +} + +.literalblock+.colist, +.listingblock+.colist { + margin-top: -.5em +} + +.colist td:not([class]):first-child { + padding: .4em .75em 0; + line-height: 1; + vertical-align: top +} + +.colist td:not([class]):first-child img { + max-width: none +} + +.colist td:not([class]):last-child { + padding: .25em 0 +} + +.thumb, +.th { + line-height: 0; + display: inline-block; + border: solid 4px #fff; + -webkit-box-shadow: 0 0 0 1px #ddd; + box-shadow: 0 0 0 1px #ddd +} + +.imageblock.left, +.imageblock[style*="float:left"] { + margin: .25em .625em 1.25em 0 +} + +.imageblock.right, +.imageblock[style*="float:right"] { + margin: .25em 0 1.25em .625em +} + +.imageblock>.title { + margin-bottom: 0 +} + +.imageblock.thumb, +.imageblock.th { + border-width: 6px +} + +.imageblock.thumb>.title, +.imageblock.th>.title { + padding: 0 .125em +} + +.image.left, +.image.right { + margin-top: .25em; + margin-bottom: .25em; + display: inline-block; + line-height: 0 +} + +.image.left { + margin-right: .625em +} + +.image.right { + margin-left: .625em +} + +a.image { + text-decoration: none; + display: inline-block +} + +a.image object { + pointer-events: none +} + +sup.footnote, +sup.footnoteref { + font-size: .875em; + position: static; + vertical-align: super +} + +sup.footnote a, +sup.footnoteref a { + text-decoration: none +} + +sup.footnote a:active, +sup.footnoteref a:active { + text-decoration: underline +} + +#footnotes { + padding-top: .75em; + padding-bottom: .75em; + margin-bottom: .625em +} + +#footnotes hr { + width: 20%; + min-width: 6.25em; + margin: -.25em 0 .75em; + border-width: 1px 0 0 +} + +#footnotes .footnote { + padding: 0 .375em 0 .225em; + line-height: 1.3334; + font-size: .875em; + margin-left: 1.2em; + margin-bottom: .2em +} + +#footnotes .footnote a:first-of-type { + font-weight: bold; + text-decoration: none; + margin-left: -1.05em +} + +#footnotes .footnote:last-of-type { + margin-bottom: 0 +} + +#content #footnotes { + margin-top: -.625em; + margin-bottom: 0; + padding: .75em 0 +} + +.gist .file-data>table { + border: 0; + background: #fff; + width: 100%; + margin-bottom: 0 +} + +.gist .file-data>table td.line-data { + width: 99% +} + +div.unbreakable { + page-break-inside: avoid +} + +.big { + font-size: larger +} + +.small { + font-size: smaller +} + +.underline { + text-decoration: underline +} + +.overline { + text-decoration: overline +} + +.line-through { + text-decoration: line-through +} + +.aqua { + color: #00bfbf +} + +.aqua-background { + background-color: #00fafa +} + +.black { + color: #000 +} + +.black-background { + background-color: #000 +} + +.blue { + color: #0000bf +} + +.blue-background { + background-color: #0000fa +} + +.fuchsia { + color: #bf00bf +} + +.fuchsia-background { + background-color: #fa00fa +} + +.gray { + color: #606060 +} + +.gray-background { + background-color: #7d7d7d +} + +.green { + color: #006000 +} + +.green-background { + background-color: #007d00 +} + +.lime { + color: #00bf00 +} + +.lime-background { + background-color: #00fa00 +} + +.maroon { + color: #600000 +} + +.maroon-background { + background-color: #7d0000 +} + +.navy { + color: #000060 +} + +.navy-background { + background-color: #00007d +} + +.olive { + color: #606000 +} + +.olive-background { + background-color: #7d7d00 +} + +.purple { + color: #600060 +} + +.purple-background { + background-color: #7d007d +} + +.red { + color: #bf0000 +} + +.red-background { + background-color: #fa0000 +} + +.silver { + color: #909090 +} + +.silver-background { + background-color: #bcbcbc +} + +.teal { + color: #006060 +} + +.teal-background { + background-color: #007d7d +} + +.white { + color: #bfbfbf +} + +.white-background { + background-color: #fafafa +} + +.yellow { + color: #bfbf00 +} + +.yellow-background { + background-color: #fafa00 +} + +span.icon>.fa { + cursor: default +} + +a span.icon>.fa { + cursor: inherit +} + +.admonitionblock td.icon [class^="fa icon-"] { + font-size: 2.5em; + text-shadow: 1px 1px 2px rgba(0, 0, 0, .5); + cursor: default +} + +.admonitionblock td.icon .icon-note::before { + content: "\f05a"; + color: #19407c +} + +.admonitionblock td.icon .icon-tip::before { + content: "\f0eb"; + text-shadow: 1px 1px 2px rgba(155, 155, 0, .8); + color: #111 +} + +.admonitionblock td.icon .icon-warning::before { + content: "\f071"; + color: #bf6900 +} + +.admonitionblock td.icon .icon-caution::before { + content: "\f06d"; + color: #bf3400 +} + +.admonitionblock td.icon .icon-important::before { + content: "\f06a"; + color: #bf0000 +} + +.conum[data-value] { + display: inline-block; + color: #fff !important; + background-color: rgba(0, 0, 0, .8); + -webkit-border-radius: 100px; + border-radius: 100px; + text-align: center; + font-size: .75em; + width: 1.67em; + height: 1.67em; + line-height: 1.67em; + font-family: "Open Sans", "DejaVu Sans", sans-serif; + font-style: normal; + font-weight: bold +} + +.conum[data-value] * { + color: #fff !important +} + +.conum[data-value]+b { + display: none +} + +.conum[data-value]::after { + content: attr(data-value) +} + +pre .conum[data-value] { + position: relative; + top: -.125em +} + +b.conum * { + color: inherit !important +} + +.conum:not([data-value]):empty { + display: none +} + +dt, +th.tableblock, +td.content, +div.footnote { + text-rendering: optimizeLegibility +} + +h1, +h2, +p, +td.content, +span.alt { + letter-spacing: -.01em +} + +p strong, +td.content strong, +div.footnote strong { + letter-spacing: -.005em +} + +p, +blockquote, +dt, +td.content, +span.alt { + /*font-size: 1.0625rem*/ +} + +p { + margin-bottom: 1.25rem +} + +.sidebarblock p, +.sidebarblock dt, +.sidebarblock td.content, +p.tableblock { + font-size: 1em +} + +.exampleblock>.content { + background-color: #fffef7; + border-color: #dddddd; + -webkit-box-shadow: 0 1px 4px #e0e0dc; + box-shadow: 0 1px 4px #e0e0dc +} + +.print-only { + display: none !important +} + +@page { + margin: 1.25cm .75cm +} + +@media print { + * { + -webkit-box-shadow: none !important; + box-shadow: none !important; + text-shadow: none !important + } + + html { + font-size: 80% + } + + a { + color: inherit !important; + text-decoration: underline !important + } + + a.bare, + a[href^="#"], + a[href^="mailto:"] { + text-decoration: none !important + } + + a[href^="http:"]:not(.bare)::after, + a[href^="https:"]:not(.bare)::after { + content: "("attr(href) ")"; + display: inline-block; + font-size: .875em; + padding-left: .25em + } + + abbr[title]::after { + content: " ("attr(title) ")" + } + + pre, + blockquote, + tr, + img, + object, + svg { + page-break-inside: avoid + } + + thead { + display: table-header-group + } + + svg { + max-width: 100% + } + + p, + blockquote, + dt, + td.content { + font-size: 1em; + orphans: 3; + widows: 3 + } + + h2, + h3, + #toctitle, + .sidebarblock>.content>.title { + page-break-after: avoid + } + + #toc, + .sidebarblock, + .exampleblock>.content { + background: none !important + } + + #toc { + border-bottom: 1px solid #dddddd !important; + padding-bottom: 0 !important + } + + body.book #header { + text-align: center + } + + body.book #header>h1:first-child { + border: 0 !important; + margin: 2.5em 0 1em + } + + body.book #header .details { + border: 0 !important; + display: block; + padding: 0 !important + } + + body.book #header .details span:first-child { + margin-left: 0 !important + } + + body.book #header .details br { + display: block + } + + body.book #header .details br+span::before { + content: none !important + } + + body.book #toc { + border: 0 !important; + text-align: left !important; + padding: 0 !important; + margin: 0 !important + } + + body.book #toc, + body.book #preamble, + body.book h1.sect0, + body.book .sect1>h2 { + page-break-before: always + } + + .listingblock code[data-lang]::before { + display: block + } + + #footer { + padding: 0 .9375em + } + + .hide-on-print { + display: none !important + } + + .print-only { + display: block !important + } + + .hide-for-print { + display: none !important + } + + .show-for-print { + display: inherit !important + } + +} + +@media print, amzn-kf8 { + #header>h1:first-child { + margin-top: 1.25rem + } + + .sect1 { + padding: 0 !important + } + + .sect1+.sect1 { + border: 0 + } + + #footer { + background: none + } + + #footer-text { + color: rgba(0, 0, 0, .6); + font-size: .9em + } + +} + +@media amzn-kf8 { + #header, + #content, + #footnotes, + #footer { + padding: 0 + } + +} + +/* Apache Unomi manual — aligned with unomi.apache.org design system */ +:root { + --unomi-primary: #6d5ce7; + --unomi-primary-dark: #5a48c9; + --unomi-primary-light: #ede9fe; + --unomi-primary-subtle: #f5f3ff; + --unomi-hero-accent: #a78bfa; + --unomi-text: #1e293b; + --unomi-text-muted: #64748b; + --unomi-bg: #ffffff; + --unomi-bg-soft: #f8fafc; + --unomi-border: #e2e8f0; + --unomi-glass: rgba(255, 255, 255, 0.92); + --unomi-on-dark: rgba(255, 255, 255, 0.8); + --unomi-on-dark-muted: rgba(255, 255, 255, 0.75); + --unomi-hero-label: rgba(255, 255, 255, 0.6); + --unomi-hero-link: rgba(255, 255, 255, 0.75); + --unomi-hero-accent-glow: rgba(167, 139, 250, 0.15); + --unomi-gradient-start: #312e81; + --unomi-gradient-mid: #5b21b6; + --unomi-gradient-start-fade: rgba(30, 27, 75, 0.2); + --unomi-gradient-dark: linear-gradient(135deg, var(--unomi-gradient-start) 0%, var(--unomi-gradient-mid) 50%, var(--unomi-primary) 100%); + --unomi-radius-sm: 0.5rem; + --unomi-radius: 1rem; + --unomi-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.06); + --unomi-transition: 0.25s cubic-bezier(0.4, 0, 0.2, 1); + --unomi-nav-height: 4.25rem; +} + +html { + scroll-padding-top: calc(var(--unomi-nav-height) + 1rem); +} + +body.book { + font-family: "Inter", system-ui, -apple-system, sans-serif; + color: var(--unomi-text); + background: var(--unomi-bg-soft); + padding-top: 0; + -webkit-font-smoothing: antialiased; +} + +body.book #content, +body.book #preamble { + font-family: "Inter", system-ui, -apple-system, sans-serif; +} + +body.book a { + color: var(--unomi-primary); +} + +body.book a:hover, +body.book a:focus { + color: var(--unomi-primary-dark); +} + +body.book h1, +body.book h2, +body.book h3, +body.book h4, +body.book h5, +body.book h6, +body.book #toctitle { + font-family: "Inter", system-ui, -apple-system, sans-serif !important; + color: var(--unomi-text) !important; + text-transform: none !important; + font-weight: 700 !important; + letter-spacing: -0.02em; +} + +/* Site navbar (matches unomi.apache.org white glass bar) */ +#unomi-site-nav { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 2000; + height: var(--unomi-nav-height); + box-sizing: border-box; + background: var(--unomi-glass); + -webkit-backdrop-filter: blur(12px); + backdrop-filter: blur(12px); + border-bottom: 1px solid var(--unomi-border); + box-shadow: var(--unomi-shadow-sm); +} + +#unomi-site-nav .unomi-navbar, +#unomi-site-nav .nav-inner { + max-width: 72rem; + margin: 0 auto; + height: 100%; + padding: 0 1.25rem; + display: flex; + align-items: center; + gap: 1rem; + flex-wrap: nowrap; + box-sizing: border-box; + position: relative; +} + +#unomi-site-nav .nav-toggle { + display: none; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 0.3rem; + width: 2.5rem; + height: 2.5rem; + padding: 0; + border: 1px solid var(--unomi-border); + border-radius: var(--unomi-radius-sm); + background: var(--unomi-bg); + color: var(--unomi-text); + cursor: pointer; + flex-shrink: 0; +} + +#unomi-site-nav .nav-toggle-bar { + display: block; + width: 1.1rem; + height: 2px; + background: currentColor; + border-radius: 1px; + transition: transform var(--unomi-transition), opacity var(--unomi-transition); +} + +#unomi-site-nav.nav-open .nav-toggle-bar:nth-child(1) { + transform: translateY(6px) rotate(45deg); +} + +#unomi-site-nav.nav-open .nav-toggle-bar:nth-child(2) { + opacity: 0; +} + +#unomi-site-nav.nav-open .nav-toggle-bar:nth-child(3) { + transform: translateY(-6px) rotate(-45deg); +} + +#unomi-site-nav .nav-menu { + flex: 1; + display: flex; + justify-content: flex-end; + min-width: 0; +} + +#unomi-site-nav .nav-brand { + display: inline-flex; + align-items: center; + text-decoration: none; + margin-right: auto; +} + +#unomi-site-nav .nav-brand img { + display: block; + height: 2.25rem; + width: auto; + transition: opacity var(--unomi-transition); +} + +#unomi-site-nav .nav-brand:hover img { + opacity: 0.8; +} + +#unomi-site-nav .nav-links { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.15rem 0.25rem; + list-style: none; + margin: 0; + padding: 0; +} + +#unomi-site-nav .nav-links a { + color: var(--unomi-text-muted); + text-decoration: none; + font-size: 0.9rem; + font-weight: 500; + padding: 0.5rem 0.85rem; + border-radius: var(--unomi-radius-sm); + transition: color var(--unomi-transition), background var(--unomi-transition); +} + +#unomi-site-nav .nav-links a:hover, +#unomi-site-nav .nav-links a:focus { + color: var(--unomi-primary); + background: var(--unomi-primary-subtle); + text-decoration: none; +} + +#unomi-site-nav .nav-links a.nav-cta { + background: var(--unomi-primary); + color: #fff !important; + border-radius: 999px; + padding: 0.4rem 1rem; + font-weight: 600; + font-size: 0.8rem; + margin-left: 0.5rem; +} + +#unomi-site-nav .nav-links a.nav-cta:hover { + background: var(--unomi-primary-dark); + color: #fff !important; +} + +#unomi-site-nav .nav-version { + color: var(--unomi-text-muted); + background: var(--unomi-bg-soft); + border: 1px solid var(--unomi-border); + border-radius: 999px; + padding: 0.25rem 0.75rem; + font-size: 0.75rem; + font-weight: 600; + white-space: nowrap; +} + +body.book #header > h1:first-child, +body.book #header > .details { + display: none; +} + +/* Hero cover (matches site homepage hero) */ +.cover-page.hero { + background: var(--unomi-gradient-dark); + color: #fff; + text-align: left; + padding: 3.5rem 2rem 3rem; + margin: 0 -1rem 2rem; + position: relative; + overflow: hidden; +} + +.cover-page.hero::before { + content: ""; + position: absolute; + top: -50%; + right: -20%; + width: 60%; + height: 200%; + background: radial-gradient(ellipse, var(--unomi-hero-accent-glow) 0%, transparent 70%); + pointer-events: none; +} + +.cover-page.hero::after { + content: ""; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 4rem; + background: linear-gradient(to bottom, transparent, var(--unomi-gradient-start-fade)); + pointer-events: none; +} + +.cover-page.hero > .content { + position: relative; + z-index: 1; + max-width: 48rem; + margin: 0 auto; +} + +.cover-page .section-label { + font-size: 0.75rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.1em; + color: rgba(255, 255, 255, 0.92) !important; + margin: 0 0 0.75rem; +} + +.cover-page .section-label p { + color: rgba(255, 255, 255, 0.92) !important; +} + +.cover-page .display-title { + font-size: clamp(2rem, 5vw, 3.25rem); + font-weight: 800; + letter-spacing: -0.03em; + line-height: 1.1; + color: #fff !important; + margin: 0 0 1rem; + border: none; +} + +.cover-page .text-hero-accent { + color: #e9d5ff !important; +} + +.cover-page .cover-meta, +.cover-page .lead, +.cover-page .cover-meta p, +.cover-page .lead p { + color: rgba(255, 255, 255, 0.92) !important; + font-size: 1.1rem; + line-height: 1.7; + margin: 0 0 1.5rem; +} + +.cover-page .cover-meta a, +.cover-page .lead a { + color: #fff !important; + text-decoration: underline; + text-underline-offset: 0.15em; +} + +.cover-page .cover-meta a:hover, +.cover-page .lead a:hover { + color: #f5f3ff !important; +} + +.cover-page.hero h1, +.cover-page.hero h2, +.cover-page.hero h3, +.cover-page.hero h4, +.cover-page.hero h5, +.cover-page.hero h6 { + color: #fff !important; +} + +.cover-page .cover-asf { + margin-top: 1.5rem; +} + +.cover-page .cover-asf .imageblock img { + opacity: 0.5; + filter: brightness(0) invert(1); +} + +/* Desktop layout: left sidebar TOC + nav over content column only */ +@media screen and (min-width: 1024px) { + body.book.toc2 #unomi-site-nav { + left: 15em; + right: 0; + width: auto; + } + + body.book.toc2 #toc.toc2 { + top: 0 !important; + height: 100vh !important; + padding-top: 1.25rem; + z-index: 1000; + background: var(--unomi-bg) !important; + border-right: 1px solid var(--unomi-border) !important; + } + + body.book.toc2 #content { + margin-top: 0; + padding-top: var(--unomi-nav-height); + } +} + +@media screen and (min-width: 1280px) { + body.book.toc2 #unomi-site-nav { + left: 20em; + } +} + +body.book.toc2 #toc.toc2 #toctitle { + margin-top: 0.25rem; + margin-bottom: 1rem; + color: var(--unomi-text) !important; + font-size: 1rem !important; + font-weight: 700 !important; +} + +body.book #toc { + border: 1px solid var(--unomi-border); + border-radius: var(--unomi-radius-sm); + background: var(--unomi-bg); + padding: 0.75rem 0.5rem; +} + +body.book #toc .sectlevel1 > li > a { + font-weight: 600; + color: var(--unomi-text); +} + +body.book #toc a:hover { + color: var(--unomi-primary); +} + +body.book #content { + background: var(--unomi-bg); + margin-top: 0 !important; +} + +/* Admonitions */ +.admonitionblock { + border-left: 4px solid var(--unomi-primary); + border-radius: var(--unomi-radius-sm); + background: var(--unomi-primary-subtle); +} + +.admonitionblock td.icon { + color: var(--unomi-primary-dark); +} + +/* Code blocks (dark pre like website) */ +.literalblock pre, +.listingblock pre { + border: none; + border-radius: var(--unomi-radius-sm); + background: var(--unomi-text) !important; + color: var(--unomi-border); +} + +.listingblock pre.highlight { + padding: 0; +} + +.listingblock pre.highlight code { + display: block; + padding: 1rem 1.25rem; + font-family: "JetBrains Mono", "Fira Code", ui-monospace, monospace; + font-size: 0.85rem; +} + +/* Site footer (matches unomi.apache.org dark footer) */ +#unomi-site-footer { + margin-top: 3rem; + padding: 3rem 1.25rem 2rem; + background: #212529; + color: rgba(255, 255, 255, 0.75); +} + +#unomi-site-footer .footer-inner { + max-width: 72rem; + margin: 0 auto; +} + +#unomi-site-footer .footer-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); + gap: 2rem 1.5rem; + margin-bottom: 2rem; +} + +#unomi-site-footer .footer-col-title { + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: rgba(255, 255, 255, 0.5); + margin: 0 0 0.75rem; +} + +#unomi-site-footer .footer-col ul { + list-style: none; + margin: 0; + padding: 0; +} + +#unomi-site-footer .footer-col li { + margin-bottom: 0.5rem; +} + +#unomi-site-footer .footer-link { + color: var(--unomi-hero-link); + text-decoration: none; + font-size: 0.875rem; + transition: color var(--unomi-transition); +} + +#unomi-site-footer .footer-link:hover { + color: #fff; +} + +#unomi-site-footer .footer-divider { + border: none; + border-top: 1px solid rgba(255, 255, 255, 0.15); + margin: 0 0 1.5rem; +} + +#unomi-site-footer .footer-bottom { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 1rem; +} + +#unomi-site-footer .footer-legal { + margin: 0; + font-size: 0.8rem; + line-height: 1.6; + color: rgba(255, 255, 255, 0.5); +} + +#unomi-site-footer .footer-legal a { + color: var(--unomi-hero-link); +} + +#unomi-site-footer .footer-asf img { + height: 1.875rem; + opacity: 0.5; + filter: brightness(0) invert(1); +} + +@media print { + #unomi-site-nav, + #unomi-site-footer { + display: none !important; + } + + body.book { + padding-top: 0; + background: #fff; + } + + .cover-page.hero { + break-after: page; + margin: 0; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; + } + + body.book #header > h1:first-child { + display: block; + color: var(--unomi-text) !important; + } +} + +@media screen and (max-width: 1023px) { + :root { + --unomi-nav-height: 3.75rem; + --unomi-toc-bar-height: 3.25rem; + } + + /* Override base AsciiDoc sidebar until desktop width */ + body.toc2 { + padding-left: 0 !important; + padding-right: 0 !important; + } + + #unomi-site-nav { + height: var(--unomi-nav-height); + min-height: 0; + left: 0 !important; + right: 0; + width: 100% !important; + } + + #unomi-site-nav .nav-inner { + height: 100%; + flex-wrap: nowrap; + padding: 0 1rem; + justify-content: flex-start; + gap: 0.5rem; + } + + #unomi-site-nav .nav-brand { + margin-right: auto; + } + + #unomi-site-nav .nav-version { + font-size: 0.7rem; + padding: 0.2rem 0.55rem; + } + + #unomi-site-nav .nav-toggle { + display: inline-flex; + } + + #unomi-site-nav .nav-menu { + display: none; + position: absolute; + top: 100%; + left: 0; + right: 0; + flex: none; + justify-content: stretch; + background: var(--unomi-bg); + border-bottom: 1px solid var(--unomi-border); + box-shadow: var(--unomi-shadow-sm); + padding: 0.5rem 0.75rem 0.75rem; + max-height: calc(100vh - var(--unomi-nav-height)); + overflow-y: auto; + } + + #unomi-site-nav.nav-open .nav-menu { + display: block; + } + + #unomi-site-nav .nav-links { + flex-direction: column; + align-items: stretch; + gap: 0.15rem; + } + + #unomi-site-nav .nav-links a { + display: block; + padding: 0.65rem 0.85rem; + } + + #unomi-site-nav .nav-links a.nav-cta { + margin-left: 0; + margin-top: 0.25rem; + text-align: center; + } + + body.book { + padding-top: var(--unomi-nav-height); + padding-bottom: calc(var(--unomi-toc-bar-height) + env(safe-area-inset-bottom, 0px)); + } + + body.book #content { + padding-top: 0; + } + + body.book #header { + padding: 0; + margin: 0; + min-height: 0; + } + + /* Bottom-anchored TOC bar (full width) */ + body.book #toc.toc2 { + position: fixed !important; + bottom: 0; + left: 0 !important; + right: 0; + top: auto !important; + width: 100% !important; + height: auto !important; + margin: 0; + padding: 0 !important; + padding-bottom: env(safe-area-inset-bottom, 0px) !important; + display: flex; + flex-direction: column-reverse; + max-height: min(85vh, 100%); + border: none; + border-top: 1px solid var(--unomi-border); + border-radius: var(--unomi-radius-sm) var(--unomi-radius-sm) 0 0; + background: var(--unomi-glass); + -webkit-backdrop-filter: blur(12px); + backdrop-filter: blur(12px); + overflow: hidden; + box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.1); + z-index: 1500; + } + + body.book #toc.toc2 #toctitle { + display: flex; + align-items: center; + justify-content: space-between; + min-height: var(--unomi-toc-bar-height); + margin: 0 !important; + padding: 0.75rem 1rem; + font-size: 0.95rem !important; + font-weight: 600 !important; + color: var(--unomi-text) !important; + cursor: pointer; + user-select: none; + flex-shrink: 0; + border-top: 1px solid transparent; + background: var(--unomi-bg); + } + + body.book #toc.toc2.toc-open #toctitle { + border-top-color: var(--unomi-border); + } + + body.book #toc.toc2 #toctitle::after { + content: ""; + width: 0.45rem; + height: 0.45rem; + border-right: 2px solid var(--unomi-text-muted); + border-bottom: 2px solid var(--unomi-text-muted); + transform: rotate(-135deg); + transition: transform var(--unomi-transition); + flex-shrink: 0; + margin-left: 0.5rem; + } + + body.book #toc.toc2.toc-open #toctitle::after { + transform: rotate(45deg); + margin-top: 0; + } + + body.book #toc.toc2:not(.toc-open) > ul { + display: none; + } + + body.book #toc.toc2.toc-open > ul { + display: block; + flex: 1 1 auto; + max-height: calc(85vh - var(--unomi-toc-bar-height) - env(safe-area-inset-bottom, 0px)); + overflow-y: auto; + -webkit-overflow-scrolling: touch; + padding: 0.5rem 0.75rem 0.75rem; + margin: 0; + font-size: 0.9rem; + background: var(--unomi-bg); + border-bottom: 1px solid var(--unomi-border); + } + + #unomi-site-footer { + margin-bottom: calc(var(--unomi-toc-bar-height) + env(safe-area-inset-bottom, 0px)); + } + + .cover-page.hero { + padding: 2.5rem 1.25rem 2rem; + text-align: center; + } +} + +@media screen and (min-width: 1024px) { + #unomi-site-nav .nav-toggle { + display: none !important; + } + + body.book #toc.toc2 { + bottom: auto !important; + padding-bottom: 0 !important; + flex-direction: column !important; + max-height: none !important; + border-radius: 0 !important; + border-top: none !important; + box-shadow: none !important; + z-index: 1000 !important; + } + + body.book { + padding-bottom: 0 !important; + } + + #unomi-site-footer { + margin-bottom: 0 !important; + } + + body.book #toc.toc2 #toctitle { + cursor: default; + pointer-events: none; + } + + body.book #toc.toc2 #toctitle::after { + display: none; + } + + body.book #toc.toc2 > ul { + display: block !important; + } +} +/* Tablet polish: larger touch targets in compact layout */ +@media screen and (min-width: 769px) and (max-width: 1023px) { + :root { + --unomi-nav-height: 4rem; + --unomi-toc-bar-height: 3.5rem; + } + + #unomi-site-nav .nav-inner { + padding: 0 1.5rem; + gap: 0.75rem; + } + + #unomi-site-nav .nav-brand img { + height: 2.5rem; + } + + #unomi-site-nav .nav-toggle { + width: 2.75rem; + height: 2.75rem; + } + + #unomi-site-nav .nav-version { + font-size: 0.8rem; + } + + #unomi-site-nav .nav-links a { + padding: 0.75rem 1rem; + font-size: 1rem; + } + + body.book #toc.toc2 #toctitle { + padding: 0.85rem 1.5rem; + font-size: 1rem !important; + } + + body.book #toc.toc2.toc-open > ul { + max-height: calc(75vh - var(--unomi-toc-bar-height) - env(safe-area-inset-bottom, 0px)); + padding: 0.75rem 1rem 1rem; + font-size: 1rem; + } + + #unomi-site-footer .footer-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .cover-page.hero { + padding: 3rem 2rem 2.5rem; + } +} + diff --git a/manual/src/main/asciidoc/upgrades/platform-upgrades.adoc b/manual/src/main/asciidoc/upgrades/platform-upgrades.adoc index 1430bc391..6ee34c702 100644 --- a/manual/src/main/asciidoc/upgrades/platform-upgrades.adoc +++ b/manual/src/main/asciidoc/upgrades/platform-upgrades.adoc @@ -12,7 +12,7 @@ // limitations under the License. // -[[_platform_upgrades]] +[#_platform_upgrades] === Platform upgrades This section is for Apache Unomi *maintainers* upgrading runtime platform components: Apache Karaf, JDK, CXF/Jackson/Jetty, Elasticsearch and OpenSearch clients, Log4j, integration tests, and application dependency hygiene. diff --git a/manual/src/main/asciidoc/upgrades/upgrade-cxf-jackson-jetty.adoc b/manual/src/main/asciidoc/upgrades/upgrade-cxf-jackson-jetty.adoc index 85f1d6d6b..2a29e8dd7 100644 --- a/manual/src/main/asciidoc/upgrades/upgrade-cxf-jackson-jetty.adoc +++ b/manual/src/main/asciidoc/upgrades/upgrade-cxf-jackson-jetty.adoc @@ -12,7 +12,7 @@ // limitations under the License. // -[[_upgrade_cxf_jackson_jetty]] +[#_upgrade_cxf_jackson_jetty] ==== CXF, Jackson, and Jetty (Karaf cascade libraries) Karaf upgrades change the *runtime* versions of several frameworks even when you only bump `${karaf.version}`. Unomi pins some of these in root `pom.xml` and `kar/src/main/feature/feature.xml` so compile-time, feature verify, and OSGi resolution stay aligned. diff --git a/manual/src/main/asciidoc/upgrades/upgrade-dependency-hygiene.adoc b/manual/src/main/asciidoc/upgrades/upgrade-dependency-hygiene.adoc index f362ea1bc..99dd6fc18 100644 --- a/manual/src/main/asciidoc/upgrades/upgrade-dependency-hygiene.adoc +++ b/manual/src/main/asciidoc/upgrades/upgrade-dependency-hygiene.adoc @@ -12,7 +12,7 @@ // limitations under the License. // -[[_upgrade_dependency_hygiene]] +[#_upgrade_dependency_hygiene] ==== Dependency hygiene (non-platform bumps) Not every version bump is a Karaf or persistence migration. PR2-style hygiene (UNOMI-875 `#795`, `ef10dfe15`) covers *application* dependencies that must *not* break OSGi rules established in platform upgrades. diff --git a/manual/src/main/asciidoc/upgrades/upgrade-elasticsearch.adoc b/manual/src/main/asciidoc/upgrades/upgrade-elasticsearch.adoc index 8673ee107..f1d1d1546 100644 --- a/manual/src/main/asciidoc/upgrades/upgrade-elasticsearch.adoc +++ b/manual/src/main/asciidoc/upgrades/upgrade-elasticsearch.adoc @@ -12,7 +12,7 @@ // limitations under the License. // -[[_upgrade_elasticsearch]] +[#_upgrade_elasticsearch] ==== Upgrading the Elasticsearch client Unomi does *not* embed the Elasticsearch server. It embeds the *Java API client* and REST transport inside the `unomi-persistence-elasticsearch-core` OSGi bundle, then exports query DSL types for the conditions bundle. diff --git a/manual/src/main/asciidoc/upgrades/upgrade-integration-tests.adoc b/manual/src/main/asciidoc/upgrades/upgrade-integration-tests.adoc index 2e839c36f..1b40307e6 100644 --- a/manual/src/main/asciidoc/upgrades/upgrade-integration-tests.adoc +++ b/manual/src/main/asciidoc/upgrades/upgrade-integration-tests.adoc @@ -12,7 +12,7 @@ // limitations under the License. // -[[_upgrade_integration_tests]] +[#_upgrade_integration_tests] ==== Integration tests and Docker Platform upgrades are validated by *Pax Exam* integration tests plus *Docker-backed* search engines. Failures often appear in Maven Failsafe output, not in `karaf.log`. @@ -117,7 +117,7 @@ Shipped: `elasticsearch-maven-plugin` replaced with `io.fabric8:docker-maven-plu Always run `docker pull` for both images before bumping version properties. -[[_upgrade_integration_tests_pax_exam_probe]] +[#_upgrade_integration_tests_pax_exam_probe] ===== Pax Exam probe and static initialization (Karaf 4.4.11 / Pax Exam 4.14) After a Karaf bump, integration tests may fail *before Unomi starts*. `karaf.log` can look healthy or incomplete while Failsafe reports hundreds of errors in a few seconds — that pattern almost always means the *PAXEXAM-PROBE* bundle failed to load test classes, not a Karaf startup crash. diff --git a/manual/src/main/asciidoc/upgrades/upgrade-java.adoc b/manual/src/main/asciidoc/upgrades/upgrade-java.adoc index 6838d3f1d..790f8105e 100644 --- a/manual/src/main/asciidoc/upgrades/upgrade-java.adoc +++ b/manual/src/main/asciidoc/upgrades/upgrade-java.adoc @@ -12,7 +12,7 @@ // limitations under the License. // -[[_upgrade_java]] +[#_upgrade_java] ==== Upgrading the JDK A JDK upgrade is almost always bundled with a *major* Karaf migration. Unomi 3.x baseline is *Java 17* (UNOMI-876, `da9c57fd2`). diff --git a/manual/src/main/asciidoc/upgrades/upgrade-jira-reference.adoc b/manual/src/main/asciidoc/upgrades/upgrade-jira-reference.adoc index 954d4ad65..628c624c7 100644 --- a/manual/src/main/asciidoc/upgrades/upgrade-jira-reference.adoc +++ b/manual/src/main/asciidoc/upgrades/upgrade-jira-reference.adoc @@ -12,7 +12,7 @@ // limitations under the License. // -[[_upgrade_jira_reference]] +[#_upgrade_jira_reference] ==== JIRA reference (platform upgrades) This section maps Apache Unomi JIRA tickets to the upgrade runbooks in this chapter. Use it when you need *why* a constraint exists (Cellar removal, CXF 3.x ceiling, ES client decoupling) or when tracing work under the Unomi 3 epic. diff --git a/manual/src/main/asciidoc/upgrades/upgrade-karaf.adoc b/manual/src/main/asciidoc/upgrades/upgrade-karaf.adoc index 3a31e4110..c45e5333e 100644 --- a/manual/src/main/asciidoc/upgrades/upgrade-karaf.adoc +++ b/manual/src/main/asciidoc/upgrades/upgrade-karaf.adoc @@ -12,7 +12,7 @@ // limitations under the License. // -[[_upgrade_karaf]] +[#_upgrade_karaf] ==== Upgrading Apache Karaf Karaf upgrades touch the *whole distribution*, not a single module. Scope depends on whether you are doing a *major*, *minor*, or *patch* Karaf bump. diff --git a/manual/src/main/asciidoc/upgrades/upgrade-log4j.adoc b/manual/src/main/asciidoc/upgrades/upgrade-log4j.adoc index 073effd11..3284e9ef3 100644 --- a/manual/src/main/asciidoc/upgrades/upgrade-log4j.adoc +++ b/manual/src/main/asciidoc/upgrades/upgrade-log4j.adoc @@ -12,7 +12,7 @@ // limitations under the License. // -[[_upgrade_log4j]] +[#_upgrade_log4j] ==== Upgrading Log4j Unomi uses *pax-logging-log4j2* as the platform logging implementation. Custom appenders live in `extensions/log4j-extension` as an OSGi *fragment* on pax-logging. Persistence bundles must *never embed* log4j JARs and must not call Log4j APIs at runtime. @@ -64,7 +64,7 @@ end note Persistence ES/OS cores must *not* call Log4j APIs at runtime. REST client verbosity is configured in `etc/org.ops4j.pax.logging.cfg` (see <<_upgrade_log4j_rest_client,REST client log levels>>). -[[_upgrade_log4j_rest_client]] +[#_upgrade_log4j_rest_client] ===== REST client log levels Elasticsearch and OpenSearch Java clients log through Apache Commons Logging → SLF4J → pax-logging. Do *not* set levels in persistence `start()` methods or persistence CM configs. diff --git a/manual/src/main/asciidoc/upgrades/upgrade-opensearch.adoc b/manual/src/main/asciidoc/upgrades/upgrade-opensearch.adoc index 7219c3667..887094f83 100644 --- a/manual/src/main/asciidoc/upgrades/upgrade-opensearch.adoc +++ b/manual/src/main/asciidoc/upgrades/upgrade-opensearch.adoc @@ -12,7 +12,7 @@ // limitations under the License. // -[[_upgrade_opensearch]] +[#_upgrade_opensearch] ==== Upgrading the OpenSearch client OpenSearch persistence mirrors Elasticsearch: a fat OSGi bundle embeds the Java client and HTTP stack (UNOMI-828, `078ebceda`). diff --git a/manual/src/main/asciidoc/upgrades/upgrade-overview.adoc b/manual/src/main/asciidoc/upgrades/upgrade-overview.adoc index 75dc91e89..21c3315f1 100644 --- a/manual/src/main/asciidoc/upgrades/upgrade-overview.adoc +++ b/manual/src/main/asciidoc/upgrades/upgrade-overview.adoc @@ -12,7 +12,7 @@ // limitations under the License. // -[[_platform_upgrade_overview]] +[#_platform_upgrade_overview] ==== Overview and shared rules ===== Guide index diff --git a/manual/src/main/asciidoc/useful-unomi-urls.adoc b/manual/src/main/asciidoc/useful-unomi-urls.adoc index 49b054659..e1bc00733 100644 --- a/manual/src/main/asciidoc/useful-unomi-urls.adoc +++ b/manual/src/main/asciidoc/useful-unomi-urls.adoc @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // -[[_useful_apache_unomi_urls]] +[#_useful_apache_unomi_urls] === Useful Apache Unomi URLs In this section we will list some useful URLs that can be used to quickly access parts of Apache Unomi that can help @@ -81,4 +81,60 @@ where PROFILE_ID is a profile identifier. This will indeed retrieve all the even |/cxs/rules/statistics |DELETE |Reset all rule execution statistics to 0 + +|/cxs/tasks +|GET +|Listing scheduled background tasks (see <<_scheduler,Task scheduler>>) (filter with `?status=` or `?type=`) + +|/cxs/tasks/{taskId} +|GET +|View a single scheduled task + +|/cxs/tasks/{taskId} +|DELETE +|Cancel a scheduled task + +|/cxs/tasks/{taskId}/retry +|POST +|Retry a failed task (`?resetFailureCount=true` optional) + +|/cxs/tasks/{taskId}/resume +|POST +|Resume a crashed task from checkpoint + +|/cxs/tenants +|GET +|List all tenants (system administrator only). See <<_multitenancy,Multi-tenancy>>. + +|/cxs/tenants/{tenantId} +|GET +|Get one tenant (system administrator or tenant administrator for that tenant). + +|/cxs/tenants +|POST +|Create a tenant (system administrator). Returns generated public and private API keys. + +|/cxs/tenants/{tenantId}/apikeys +|POST +|Generate a new API key (`?type=PUBLIC` or `PRIVATE`, optional `validityDays`). + +|/cxs/tenants/{tenantId}/usage +|GET +|Tenant usage snapshot (`?period=current-month` or `YYYY-MM`). + +|/cxs/tenants/{tenantId}/purge/events +|POST +|Purge events older than `retentionDays` (destructive; tenant administrator). + +|/cxs/cluster +|GET +|List cluster nodes and statistics. See <<_clustering,Cluster setup>>. + +|/health/check +|GET +|Health check JSON (role `health`, default user `health`/`health`). See <<_health_check,Health Check extension>>. + +|/cxs/context.json?explain=true +|POST +|Context request with request tracing (admin roles). See <<_request_tracing_explain,Using the explain parameter for request tracing>>. |=== diff --git a/manual/src/main/asciidoc/web-tracker.adoc b/manual/src/main/asciidoc/web-tracker.adoc index 051036b86..0ec98d0fe 100644 --- a/manual/src/main/asciidoc/web-tracker.adoc +++ b/manual/src/main/asciidoc/web-tracker.adoc @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // -[[_unomi_web_tracker_reference]] +[#_unomi_web_tracker_reference] === Unomi Web Tracker reference In this section of the documentation, more details are provided about the web tracker provided by Unomi. diff --git a/manual/src/main/asciidoc/whats-new.adoc b/manual/src/main/asciidoc/whats-new.adoc index febe1479a..812dcff4b 100644 --- a/manual/src/main/asciidoc/whats-new.adoc +++ b/manual/src/main/asciidoc/whats-new.adoc @@ -11,58 +11,85 @@ // See the License for the specific language governing permissions and // limitations under the License. // -=== What's new in Apache Unomi 3.0 +[#_whats_new_3_1] +=== What's new in Apache Unomi 3.1 + +Apache Unomi 3.1 builds on the 3.0 platform (Elasticsearch 9 client, Karaf 4.4, Java 17) with operator-focused features for multi-tenant production deployments. + +==== Multi-tenancy and API keys + +Complete tenant isolation for profiles, events, segments, rules, and schemas. Public endpoints (for example `/cxs/context.json`) require a tenant public API key; administrative work uses tenant private keys or system administrator credentials. + +* Operator guide: <<_multitenancy,Multi-tenancy>> +* Migration: <<_migrate_from_3_0_to_3_1,Migrate from 3.0 to 3.1>> +* Migrating from Unomi 2.x: <<_v2_compatibility_mode,V2 compatibility mode>> (`v2.compatibilitymode.enabled` in `org.apache.unomi.rest.authentication.cfg`) + +==== Cluster-aware task scheduler + +Built-in background job scheduler with persistence, cluster locks, recovery, REST API (`/cxs/tasks`), and Karaf shell commands. + +* <<_scheduler,Task scheduler>> + +[#_opensearch_support] +==== OpenSearch 3 support -Apache Unomi 3 is a new release focused on integrations of the client to support elasticsearch version 9. -It also include the upgrade of the Karaf version. +Official alternative to Elasticsearch, including secured Docker deployments. -==== Elasticsearch client upgrade +* Configuration: <<_configuration,Configuration>> +* Migration from Elasticsearch: <<_migrate_from_elasticsearch_to_opensearch,Migrate from Elasticsearch to OpenSearch>> -The official client for Elasticsearch has been added to Apache Unomi in version 3.0 in order to replace the old rest-client which -is not supported anymore. +==== Persistence-based clustering -The documentation of the client can be found here: https://www.elastic.co/docs/reference/elasticsearch/clients/java +Cluster node registry and heartbeats through Elasticsearch or OpenSearch (no Karaf Cellar). See <<_clustering,Cluster setup>>. -=== Elasticsearch 7 data migration +==== Tenant usage and retention -A procedure to migrate your data from Elasticsearch 7 to Elasticsearch 9 can be found in the <<_migrate_from_elasticsearch_7_to_elasticsearch_9,Migrate from Elasticsearch 7 to Elasticsearch 9>> section +Read-only per-tenant usage metrics and an event retention purge API for upstream control planes. + +* REST: `/cxs/tenants/{tenantId}/usage` and `/cxs/tenants/{tenantId}/purge/events` (see <<_useful_apache_unomi_urls,Useful URLs>>) + +==== Request tracing + +Administrators can trace how context and event-collector requests are processed using `explain=true` on supported endpoints. + +* <<_request_tracing_explain,Using the explain parameter for request tracing>> + +[#_whats_new_condition_validation] +==== Condition validation + +Segments, rules, and queries are validated against condition type definitions at save time. +Invalid parameters are rejected before they reach runtime evaluation. + +* <<_condition_validation,Condition validation>> +* <<_debugging_conditions,Debugging conditions>> + +=== What's new in Apache Unomi 3.0 -=== Karaf upgrade +Apache Unomi 3.0 is a major platform release. It upgrades the search-engine client, the OSGi runtime, and the minimum Java version. -The Karaf version has been upgraded from 4.2.15 to 4.4.8 in order to support the latest versions of the dependencies. -This upgrade also brings support for Java 17. +==== Java 17 -==== OpenSearch Support +Unomi 3.0 requires **Java 17** or later. Java 11 is no longer supported. -Starting with version 3.1.0, Apache Unomi now officially supports OpenSearch 3 as an alternative to Elasticsearch. This addition gives users more flexibility in choosing their search engine backend. Key features include: +==== Karaf 4.4 -- Full support for OpenSearch 3 -- Seamless integration with existing Unomi features -- Support for security features enabled by default in OpenSearch -- Compatible with both standalone and Docker deployments +Karaf was upgraded from 4.2.x to **4.4.11** to support current dependencies and Java 17. -===== Configuration Options +==== Elasticsearch 9 Java API client -Users can choose between ElasticSearch and OpenSearch through various configuration methods: +The legacy Elasticsearch REST high-level client was replaced by the official **Elasticsearch 9** Java API client. -1. Using configuration properties -2. Using Maven profiles during build -3. Using Docker environment variables +* Client reference: https://www.elastic.co/docs/reference/elasticsearch/clients/java +* Data migration from Elasticsearch 7: <<_migrate_from_elasticsearch_7_to_elasticsearch_9,Migrate from Elasticsearch 7 to Elasticsearch 9>> -For detailed configuration instructions, see the <<_configuration,configuration section>>. +==== QueryBuilder ID renames -===== Security Considerations +Built-in condition `queryBuilder` IDs were renamed to remove Elasticsearch-specific suffixes. Custom plugins and JSON definitions must be updated. See the mapping table in <<_migrate_from_2_x_to_3_0,Migrate from 2.x to 3.0>>. -When using OpenSearch: -- Security is enabled by default and requires SSL/TLS -- Default admin credentials are required -- The initial admin password can be configured via environment variables +==== Persistence-based clustering -===== Migration Notes +Cluster coordination moved from Karaf Cellar to a persistence-backed node registry (see <<_clustering,Cluster setup>>). -For users wanting to migrate from ElasticSearch to OpenSearch: -- Data migration requires a full cluster shutdown -- All existing features, including the flattened field type, are supported -- Existing queries and aggregations work seamlessly with both backends +==== Health check extension -For detailed migration instructions, refer to the <<_migrations,migration guide>>. +HTTP health endpoint at `/health/check` with pluggable providers (Karaf, search engine, persistence, cluster, Unomi bundles). See <<_health_check,Health check>>. diff --git a/manual/src/main/asciidoc/writing-plugins.adoc b/manual/src/main/asciidoc/writing-plugins.adoc index b5f0f7a8b..c49808422 100644 --- a/manual/src/main/asciidoc/writing-plugins.adoc +++ b/manual/src/main/asciidoc/writing-plugins.adoc @@ -11,310 +11,160 @@ // See the License for the specific language governing permissions and // limitations under the License. // -=== Writing Plugins -Apache Unomi is architected to be extensible through plugins. However, it's important to note that plugins are only necessary when custom Java logic is required. Most common needs and use cases can now be covered using: +[#_writing_plugins] +=== Writing plugins -* Groovy actions (for custom action logic without deployment) -* Complex condition trees (for sophisticated matching logic) -* Rules (for defining business logic and automation) -* Apache Unomi's REST API (for configuration and management) +Apache Unomi is extensible through OSGi plugins (Karaf bundles). Many use cases do **not** need a plugin: -These built-in features allow you to implement most functionality without deploying any code, making the system more maintainable and reducing development overhead. You should only consider developing a plugin when you need to: +* Groovy actions — custom rule actions stored at runtime (see <<_configuration,Configuration>>) +* Complex condition trees — combine built-in condition types +* Rules, segments, and REST configuration — manage through APIs or JSON definitions in a bundle +* <<_json_schemas,JSON Schemas>> — validate events without Java code -* Implement custom Java-based query builders for search engine integration -* Add new core system functionality that requires deep integration -* Optimize performance-critical operations with compiled code -* Integrate with external systems requiring complex Java libraries +Build a plugin when you need compiled Java logic, for example: -=== Types vs. instances +* Custom `ConditionEvaluator` or search-engine `*QueryBuilder` implementations +* Custom `ActionExecutor` integrations with external systems +* Index mappings, Painless scripts, or migration patches shipped with your feature +* Background work through `TaskExecutor` and `SchedulerService` (see <<_scheduler,Task scheduler>>) -Several extension points in Unomi rely on the concept of type: a plugin defines a prototype for what the actual -items will be once parameterized with values known only at runtime. This is similar to the concept of classes in -object-oriented programming: types define classes, providing the expected structure and which fields are expected to -be provided at runtime, that are then instantiated when needed with actual values. +==== Prerequisites -So for example we have the following types vs instances: +* **Java 17** or later +* **Apache Karaf 4.4** (bundled with Unomi distributions) +* Maven `packaging` set to `bundle` (Felix maven-bundle-plugin) +* Depend on `unomi-api` and import `unomi-bom` for aligned artifact versions -- ConditionTypes vs Conditions -- ActionTypes vs Actions -- PropertyTypes vs Properties (for profiles and sessions) +See also: -=== Plugin structure +* <<_data_model_overview,Data model overview>> +* <<_builtin_condition_types,Built-in condition types>> — registration patterns for Unomi 3.0+ +* <<_builtin_action_types,Built-in action types>> +* <<_condition_validation,Condition validation>> — `validation` metadata on condition parameters -Being built on top of Apache Karaf, Unomi leverages OSGi to support plugins. A Unomi plugin is, thus, an OSGi -bundle specifying some specific metadata to tell Unomi the kind of entities it provides. A plugin can provide the -following entities to extend Unomi, each with its associated definition (as a JSON file), located in a specific spot -within the `META-INF/cxs/` directory of the bundle JAR file: +==== Types vs instances -|==== -|Entity |Location in `cxs` directory +Extension points follow a type/instance model (like classes and objects): -|ActionType |actions -|ConditionType |conditions -|Persona |personas -|PropertyMergeStrategyType |mergers -|PropertyType |properties then profiles or sessions subdirectory then `<category name>` directory -|Rule |rules -|Scoring |scorings -|Segment |segments -|ValueType |values -|==== +* *ConditionType* → *Condition* instances in segments, rules, and queries +* *ActionType* → *Action* instances executed by rules +* *PropertyType* → profile or session property metadata (UI hints; not server-side validation) -http://aries.apache.org/modules/blueprint.html[Blueprint] is used to declare what the plugin provides and inject -any required dependency. The Blueprint file is located, as usual, at `OSGI-INF/blueprint/blueprint.xml` in the bundle JAR file. +==== `META-INF/cxs/` layout -=== Search Engine Specific Implementations +A Unomi plugin is an OSGi bundle. JSON definitions and bundle resources live under `src/main/resources/META-INF/cxs/` (and related paths below). -When implementing plugins that need to interact with the search engine layer (ElasticSearch or OpenSearch), special consideration must be taken to ensure compatibility and proper deployment. +[cols="2,3,2", options="header"] +|=== +| Entity | Directory under `META-INF/cxs/` | Auto-loaded at bundle start? -==== Search Engine Specific Bundles +| ActionType | `actions/` | Yes +| ConditionType | `conditions/` | Yes +| ValueType | `values/` | Yes +| PropertyMergeStrategyType | `mergers/` | Yes +| PropertyType | `properties/profiles/...` or `properties/sessions/...` | Yes +| Rule | `rules/` | Yes +| Segment | `segments/` | Yes +| Scoring | `scoring/` | Yes +| Persona | `personas/` | Yes +| Patch | `patches/` | Yes (first run) +| Index mapping | `mappings/` | Loaded by persistence layer +| JSON Schema | `schemas/` | Loaded by schema service +| Expression filter | `expressions/` | Loaded by scripting layer +| Painless script | `painless/` | Loaded by persistence layer +| Migration script | `migration/` | Used by shell migration tooling +|=== -For plugins that provide search query builder implementations, you should structure your plugin with three bundles: -1. A common bundle for shared code and resources -2. An ElasticSearch-specific implementation bundle -3. An OpenSearch-specific implementation bundle +Goals and campaigns can be deployed with `unomi:deploy-definition` but are not auto-loaded from bundles the same way as rules and segments. -Example structure: -``` -my-plugin/ -├── common/ -│ ├── src/main/java/.../ -│ │ ├── model/ -│ │ │ └── MyCustomCondition.java -│ │ └── services/ -│ │ └── MyCustomService.java -│ ├── src/main/resources/META-INF/cxs/ -│ │ ├── conditions/ -│ │ │ └── my-custom-condition.json -│ │ └── actions/ -│ │ └── my-custom-action.json -│ └── pom.xml -├── elasticsearch/ -│ ├── src/main/java/.../ -│ │ └── impl/ -│ │ └── MyElasticSearchQueryBuilder.java -│ ├── src/main/resources/META-INF/cxs/ -│ │ └── mappings/ -│ │ └── my-index-mapping.json -│ └── pom.xml -├── opensearch/ -│ ├── src/main/java/.../ -│ │ └── impl/ -│ │ └── MyOpenSearchQueryBuilder.java -│ ├── src/main/resources/META-INF/cxs/ -│ │ └── mappings/ -│ │ └── my-index-mapping.json -│ └── pom.xml -└── pom.xml -``` - -Example POM dependencies: - -Common Bundle (my-plugin-common): -[source,xml] ----- - - - org.apache.unomi - unomi-api - ${unomi.version} - provided - - ----- +Tags are not separate JSON files — use `systemTags` on metadata inside other definitions. -ElasticSearch Bundle (my-plugin-elasticsearch): -[source,xml] ----- - - - my.group.id - my-plugin-common - ${project.version} - - - org.apache.unomi - unomi-persistence-elasticsearch-core - ${unomi.version} - provided - - ----- - -OpenSearch Bundle (my-plugin-opensearch): -[source,xml] ----- - - - my.group.id - my-plugin-common - ${project.version} - - - org.apache.unomi - unomi-persistence-opensearch-core - ${unomi.version} - provided - - ----- +==== OSGi service registration (Declarative Services) -==== Configuration and Deployment +New plugins must use **OSGi Declarative Services** (`@Component`, `@Reference`) — not Blueprint XML. +Unomi is migrating away from Blueprint; legacy bundles such as `plugins/baseplugin` and parts of `persistence-*` still ship Blueprint wiring, but that is not a template for new code. -Administrators can control which search engine implementation to use by setting up a distribution. This distribution 'macro' feature determines which features (including your plugin's bundles) are deployed based on the chosen search engine: +Enable DS component generation in your bundle POM: [source,xml] ---- - - - mvn:my.group.id/my-plugin-common/${project.version} - mvn:my.group.id/my-plugin-${search.engine}/${project.version} - - - - unomi-base - unomi-startup - unomi-elasticsearch-core - unomi-persistence-core - unomi-services - unomi-rest-api - unomi-cxs-lists-extension - unomi-cxs-geonames-extension - unomi-cxs-privacy-extension - unomi-elasticsearch-conditions - unomi-plugins-base - unomi-plugins-request - unomi-plugins-mail - unomi-wab - unomi-web-tracker - unomi-healthcheck-elasticsearch - unomi-router-karaf-feature - unomi-groovy-actions - unomi-rest-ui - unomi-startup-complete - my-plugin-feature - - + + org.osgi + org.osgi.service.component.annotations + provided + ---- -==== Custom Plugins - -The plugin otherwise follows a regular maven project layout and should depend on the Unomi API maven artifact: - [source,xml] ---- - - org.apache.unomi - unomi-api - ... - + + org.apache.felix + maven-bundle-plugin + + + <_dsannotations>* + + + ---- -Some plugins consists only of JSON definitions that are used to instantiate the appropriate structures at runtime -while some more involved plugins provide code that extends Unomi in deeper ways. - -In both cases, plugins can provide more that one type of extension. For example, a plugin could provide both `ActionType`s and `ConditionType`s. - -=== Extension points - -In this section the value types that may be used as extension points are presented. Examples of these types will be -given in the next section with more details. - -==== ActionType - -`ActionType`s define new actions that can be used as consequences of Rules being triggered. When a rule triggers, it -creates new actions based on the event data and the rule internal processes, providing values for parameters defined -in the associated `ActionType`. Example actions include: “Set user property x to value y” or “Send a message to service x”. - -==== ConditionType - -`ConditionType`s define new conditions that can be applied to items (for example to decide whether a rule needs to be -triggered or if a profile is considered as taking part in a campaign) or to perform queries against the stored Unomi -data. They may be implemented in Java when attempting to define a particularly complex test or one that can better be -optimized by coding it. They may also be defined as combination of other conditions. A simple condition could be: -“User is male”, while a more generic condition with parameters may test whether a given property has a specific value: -“User property x has value y”. - -==== Persona - -A persona is a "virtual" profile used to represent categories of profiles, and may also be used to test how a -personalized experience would look like using this virtual profile. A persona can define predefined properties and -sessions. Persona definition make it possible to “emulate” a certain type of profile, e.g : US visitor, non-US visitor, etc. - -==== PropertyMergeStrategyType - -A strategy to resolve how to merge properties when merging profile together. - -==== PropertyType +Follow `plugins/advanced-conditions/pom.xml` for a minimal DS-enabled plugin module. -Definition for a profile, session, or event property, specifying how possible values are constrained, if the value is -multi-valued (a vector of values as opposed to a scalar value). `PropertyType`s can also be categorized using -systemTags or file system structure, using sub-directories to organize definition files. +Service property keys: -For detailed information about property types, see <<_property_types,Property Types>>. +[cols="2,2,3", options="header"] +|=== +| Extension | Service interface | OSGi property key -==== Rule +| Condition evaluator | `org.apache.unomi.persistence.spi.conditions.evaluator.ConditionEvaluator` | `conditionEvaluatorId` +| Elasticsearch query builder | `org.apache.unomi.persistence.elasticsearch.ConditionESQueryBuilder` | `queryBuilderId` +| OpenSearch query builder | `org.apache.unomi.persistence.opensearch.ConditionOSQueryBuilder` | `queryBuilderId` +| Action executor | `org.apache.unomi.api.actions.ActionExecutor` | `actionExecutorId` +| Value type validator | `org.apache.unomi.api.services.ValueTypeValidator` | (service type only) +|=== -`Rule`s are conditional sets of actions to be executed in response to incoming events. Triggering of rules is guarded -by a condition: the rule is only triggered if the associated condition is satisfied. That condition can test the -event itself, but also the profile or the session. Once a rule triggers, a list of actions can be performed as -consequences. Also, when rules trigger, a specific event is raised so that other parts of Unomi can react accordingly. +The JSON descriptor field (`conditionEvaluator`, `queryBuilder`, `actionExecutor`) must match the OSGi property value. -==== Scoring +==== Creating a Maven plugin project -`Scoring`s are set of conditions associated with a value to assign to profiles when matching so that the associated -users can be scored along that dimension. Each scoring element is evaluated and matching profiles' scores are -incremented with the associated value. +A minimal plugin is a Maven bundle project. Prefer importing `unomi-bom` rather than pinning versions manually. -==== Segments - -`Segment`s represent dynamically evaluated groups of similar profiles in order to categorize the associated users. -To be considered part of a given segment, users must satisfies the segment’s condition. If they match, users are -automatically added to the segment. Similarly, if at any given point during, they cease to satisfy the segment’s -condition, they are automatically removed from it. - -==== Tag - -`Tag`s are simple labels that are used to classify all other objects inside Unomi. - -==== ValueType - -Definition for values that can be assigned to properties ("primitive" types). - -=== Custom plugins - -Apache Unomi is a pluggeable server that may be extended in many ways. This document assumes you are familiar with the -<<_data_model_overview,Apache Unomi Data Model>> . This document is mostly a reference document on the different things that may -be used inside an extension. If you are looking for complete samples, please see the <<_samples,samples page>>. - -==== Creating a plugin - -An plugin is simply a Maven project, with a Maven pom that looks like this: - -[source] +[source,xml] ---- - - - org.apache.unomi - unomi-plugins - ${project.version} - - + 4.0.0 - unomi-plugin-example - Apache Unomi :: Plugins :: Example - A sample example of a Unomi plugin - ${project.version} + com.example + my-unomi-plugin + 1.0.0-SNAPSHOT bundle + + 3.1.0-SNAPSHOT + + + + + + org.apache.unomi + unomi-bom + ${unomi.version} + pom + import + + + + - org.apache.unomi unomi-api - ${project.version} + provided + + + org.osgi + org.osgi.service.component.annotations provided @@ -324,14 +174,13 @@ An plugin is simply a Maven project, with a Maven pom that looks like this: org.apache.felix maven-bundle-plugin + 5.1.9 true *;scope=compile|runtime - - sun.misc;resolution:=optional, - * - + <_dsannotations>* + * @@ -340,757 +189,459 @@ An plugin is simply a Maven project, with a Maven pom that looks like this: ---- -A plugin may contain many different kinds of Apache Unomi objects, as well as custom OSGi services or anything that -is needed to build your application. +==== Deployment and updates -==== Deployment and custom definition +When a bundle starts, predefined JSON definitions are deployed **once** if they do not already exist. +Redeploying the same bundle does not overwrite existing definitions. -When you deploy a custom bundle with a custom definition (see "Predefined xxx" chapters under) for the first time, the -definition will automatically be deployed at your bundle start event *if it does not exist*. -After that if you redeploy the same bundle, the definition will not be redeployed, but you can redeploy it manually -using the command `unomi:deploy-definition <bundleId> <fileName>` If you need to modify an existing -definition when deploying the module, see <<_migration_patches,Migration patches>>. +To force redeploy: -==== Predefined segments +* Karaf shell: `unomi:deploy-definition ` — see the shell commands chapter in this manual +* Structural changes to existing definitions: <<_migration_patches,Migration patches>> -You may provide pre-defined segments by simply adding a JSON file in the src/main/resources/META-INF/cxs/segments directory of -your Maven project. Here is an example of a pre-defined segment: +Wildcards are supported, for example `unomi:deploy-definition 175 rules *`. -[source] +==== JSON-only extensions + +===== Predefined segments + +Place JSON under `META-INF/cxs/segments/`: + +[source,json] ---- { "metadata": { "id": "leads", "name": "Leads", "scope": "systemscope", - "description": "You can customize the list below by editing the leads segment.", - "readOnly":true + "description": "Profiles with leadAssignedTo set", + "readOnly": true }, "condition": { + "type": "booleanCondition", "parameterValues": { + "operator": "and", "subConditions": [ { + "type": "profilePropertyCondition", "parameterValues": { "propertyName": "properties.leadAssignedTo", "comparisonOperator": "exists" - }, - "type": "profilePropertyCondition" + } } - ], - "operator" : "and" - }, - "type": "booleanCondition" + ] + } } } ---- -Basically this segment uses a condition to test if the profile has a property `leadAssignedTo` that exists. All profiles -that match this condition will be part of the pre-defined segment. +===== Predefined rules -==== Predefined rules +Place JSON under `META-INF/cxs/rules/`: -You may provide pre-defined rules by simply adding a JSON file in the src/main/resources/META-INF/cxs/rules directory of -your Maven project. Here is an example of a pre-defined rule: - -[source] +[source,json] ---- { - "metadata" : { - "id": "evaluateProfileSegments", - "name": "Evaluate segments", - "description" : "Evaluate segments when a profile is modified", - "readOnly":true - }, - - "condition" : { - "type": "profileUpdatedEventCondition", - "parameterValues": { - } - }, - - "actions" : [ - { - "type": "evaluateProfileSegmentsAction", - "parameterValues": { - } - } - ] - + "metadata": { + "id": "evaluateProfileSegments", + "name": "Evaluate segments", + "description": "Evaluate segments when a profile is modified", + "readOnly": true + }, + "condition": { + "type": "profileUpdatedEventCondition", + "parameterValues": {} + }, + "actions": [ + { + "type": "evaluateProfileSegmentsAction", + "parameterValues": {} + } + ] } ---- -In this example we provide a rule that will execute when a predefined composed condition of type -"profileUpdatedEventCondition" is received. See below to see how predefined composed conditions are declared. -Once the condition is matched, the actions will be executed in sequence. In this example there is only a single -action of type "evaluateProfileSegmentsAction" that is defined so it will be executed by Apache Unomi's rule engine. -You can also see below how custom actions may be defined. - -==== Predefined properties +===== Predefined property types -By default Apache Unomi comes with a set of pre-defined properties, but in many cases it is useful to add additional -predefined property definitions. You can create property definitions for session or profile properties by creating them -in different directories. +* Profiles: `META-INF/cxs/properties/profiles/` +* Sessions: `META-INF/cxs/properties/sessions/` -For session properties you must create a JSON file in the following directory in your Maven project: +See <<_property_types,Property types>> for the full model. -[source] ----- -src/main/resources/META-INF/cxs/properties/sessions ----- +===== Predefined child conditions -For profile properties you must create the JSON file inside the directory in your Maven project: - -[source] ----- -src/main/resources/META-INF/cxs/properties/profiles ----- +Specialize an existing condition type with `parentCondition` under `META-INF/cxs/conditions/`: -Here is an example of a property definition JSON file - -[source] ----- -{ - "metadata": { - "id": "city", - "name": "City", - "systemTags": ["properties", "profileProperties", "contactProfileProperties"] - }, - "type": "string", - "defaultValue": "", - "automaticMappingsFrom": [ ], - "rank": "304.0" -} ----- - -==== Predefined child conditions - -You can define new predefined conditions that are actually conditions inheriting from a parent condition and setting -pre-defined parameter values. You can do this by creating a JSON file in: - -[source] ----- -src/main/resources/META-INF/cxs/conditions ----- - -Here is an example of a JSON file that defines a profileUpdateEventCondition that inherits from a parent condition of -type eventTypeCondition. - -[source] +[source,json] ---- { "metadata": { "id": "profileUpdatedEventCondition", "name": "profileUpdatedEventCondition", - "description": "", - "systemTags": [ - "event", - "eventCondition" - ], + "systemTags": ["event", "eventCondition"], "readOnly": true }, "parentCondition": { "type": "eventTypeCondition", - "parameterValues": { - "eventTypeId": "profileUpdated" - } + "parameterValues": { "eventTypeId": "profileUpdated" } }, - - "parameters": [ - ] + "parameters": [] } ---- -==== Predefined personas - -Personas may also be pre-defined by creating JSON files in the following directory: - -[source] ----- -src/main/resources/META-INF/cxs/personas ----- - -Here is an example of a persona definition JSON file: - -[source] ----- -{ - "persona": { - "itemId": "usVisitor", - "properties": { - "description": "Represents a visitor browsing from inside the continental US", - "firstName": "U.S.", - "lastName": "Visitor" - }, - "segments": [] - }, - "sessions": [ - { - "itemId": "aa3b04bd-8f4d-4a07-8e96-d33ffa04d3d9", - "profileId": "usVisitor", - "properties": { - "operatingSystemName": "OS X 10.9 Mavericks", - "sessionCountryName": "United States", - "location": { - "lat":37.422, - "lon":-122.084058 - }, - "userAgentVersion": "37.0.2062.120", - "sessionCountryCode": "US", - "deviceCategory": "Personal computer", - "operatingSystemFamily": "OS X", - "userAgentName": "Chrome", - "sessionCity": "Mountain View" - }, - "timeStamp": "2014-09-18T11:40:54Z", - "lastEventDate": "2014-09-18T11:40:59Z", - "duration": 4790 - } - ] -} ----- +===== Predefined personas -You can see that it's also possible to define sessions for personas. +Place JSON under `META-INF/cxs/personas/`. Each file can define a persona profile and optional sessions for testing personalization. ==== Custom action types -Custom action types are a powerful way to integrate with external systems by being able to define custom logic that will -be executed by an Apache Unomi rule. An action type is defined by a JSON file created in the following directory: - -[source] ----- -src/main/resources/META-INF/cxs/actions ----- +1. Add `META-INF/cxs/actions/myAction.json` with an `actionExecutor` ID. +2. Implement `ActionExecutor` and register it with `@Component` and matching `actionExecutorId`. -Here is an example of a JSON action definition: +JSON descriptor (same shape as `extensions/lists-extension/actions/.../addToLists.json`): -[source] +[source,json] ---- { "metadata": { - "id": "addToListsAction", - "name": "addToListsAction", - "description": "", - "systemTags": [ - "demographic", - "availableToEndUser" - ], - "readOnly": true + "id": "myNotifyAction", + "name": "myNotifyAction", + "readOnly": false }, - "actionExecutor": "addToLists", + "actionExecutor": "myNotifyAction", "parameters": [ - { - "id": "listIdentifiers", - "type": "string", - "multivalued": true - } + { "id": "message", "type": "string", "multivalued": false } ] } ---- -The `actionExecutor` identifier refers to a service property that is defined in the OSGi Blueprint service registration. -Note that any OSGi service registration may be used, but in these examples we use OSGi Blueprint. The definition for the -above JSON file will be found in a file called `src/main/resources/OSGI-INF/blueprint/blueprint.xml` with the following -content: +Java executor (Declarative Services — write this pattern in your plugin; do not copy Blueprint from older bundles): -[source] ----- - - - - - - - - - - - - - - - - - - - +[source,java] ---- +@Component(service = ActionExecutor.class, property = {"actionExecutorId=myNotifyAction"}) +public class MyNotifyAction implements ActionExecutor { -You can note here the `actionExecutorId` that corresponds to the `actionExecutor` in the JSON file. - -The implementation of the action is available here : https://github.com/apache/unomi/blob/master/extensions/lists-extension/actions/src/main/java/org/apache/unomi/lists/actions/AddToListsAction.java[org.apache.unomi.lists.actions.AddToListsAction] - -==== Custom condition types + private ProfileService profileService; -Custom condition types are different from predefined child conditions because they implement their logic using Java classes. -They are also declared by adding a JSON file into the `conditions` directory: + @Reference + public void setProfileService(ProfileService profileService) { + this.profileService = profileService; + } -[source] ----- -src/main/resources/META-INF/cxs/conditions + @Override + public int execute(Action action, Event event) { + String message = (String) action.getParameter("message"); + // use profileService / event as needed + return EventService.NO_CHANGE; + } +} ---- -Here is an example of JSON custom condition type definition: +For a maintained DS registration pattern in the tree, see `graphql/cxs-impl/.../CDPUpdateListsAction.java`. +For action business logic (lists), see `extensions/lists-extension/actions/.../AddToListsAction.java` — migrate its Blueprint wiring to DS if you use it as a starting point. -[source] ----- -{ - "metadata": { - "id": "matchAllCondition", - "name": "matchAllCondition", - "description": "", - "systemTags": [ - "logical", - "profileCondition", - "eventCondition", - "sessionCondition", - "sourceEventCondition" - ], - "readOnly": true - }, - "conditionEvaluator": "matchAllConditionEvaluator", - "queryBuilder": "matchAllConditionQueryBuilder", +Catalog of built-in actions: <<_builtin_action_types,Built-in action types>>. - "parameters": [ - ] -} ----- +==== Custom condition types -Note the `conditionEvaluator` and the `queryBuilder` values. These reference OSGi service properties that are declared -in an OSGi Blueprint configuration file (other service definitions may also be used such as Declarative Services or even -Java registered services). Here is an example of an OSGi Blueprint definition corresponding to the above JSON condition -type definition file. +Custom condition types combine: -[source] ----- -src/main/resources/OSGI-INF/blueprint/blueprint.xml - - - - - - - - - - - - - - - - - - ----- +* A JSON descriptor in `META-INF/cxs/conditions/` +* A `ConditionEvaluator` OSGi service (required for rule and event evaluation) +* `ConditionESQueryBuilder` / `ConditionOSQueryBuilder` services (required when the condition is used in segments or profile queries) -You can find the implementation of the two classes here : +Unomi 3.0+ uses **engine-neutral** `queryBuilder` IDs in JSON (for example `booleanConditionQueryBuilder`, not `*ESQueryBuilder`). -* https://github.com/apache/unomi/blob/master/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/MatchAllConditionESQueryBuilder.java[conditions.org.apache.unomi.persistence.elasticsearch.MatchAllConditionESQueryBuilder] -* https://github.com/apache/unomi/blob/master/plugins/baseplugin/src/main/java/org/apache/unomi/plugins/baseplugin/conditions/MatchAllConditionEvaluator.java[org.apache.unomi.plugins.baseplugin.conditions.MatchAllConditionEvaluator] +Add `validation` blocks on parameters so invalid instances are rejected at save time — see <<_condition_validation,Condition validation>>. -==== Implementation Examples +===== Evaluator registration (Declarative Services) -Here's a complete example of implementing custom query builders for both search engines following Apache Unomi 3.0 best practices: +Reference implementation: `plugins/advanced-conditions` (`SourceEventPropertyConditionEvaluator`, `PastEventConditionEvaluator`). -**ElasticSearch Implementation:** [source,java] ---- -package org.apache.unomi.plugin.elasticsearch; +@Component(service = ConditionEvaluator.class, + property = {"conditionEvaluatorId=sourceEventPropertyConditionEvaluator"}) +public class SourceEventPropertyConditionEvaluator implements ConditionEvaluator { -import co.elastic.clients.elasticsearch._types.query_dsl.Query; -import org.apache.unomi.api.conditions.Condition; -import org.apache.unomi.persistence.elasticsearch.ConditionESQueryBuilder; -import org.elasticsearch.index.query.QueryBuilder; -import org.elasticsearch.index.query.QueryBuilders; + private DefinitionsService definitionsService; -public class MyCustomQueryBuilder implements ConditionESQueryBuilder { - - @Override - public Query buildQuery(Condition condition, Map context, - ConditionESQueryBuilderDispatcher dispatcher) { - // Get parameters from the condition - String fieldName = (String) condition.getParameter("fieldName"); - String fieldValue = (String) condition.getParameter("fieldValue"); - - // Build Elasticsearch-specific query using the new client - return Query.of(q -> q - .bool(b -> b - .must(m -> m - .term(t -> t - .field(fieldName) - .value(v -> v.stringValue(fieldValue)) - ) - ) - ) - ); + @Reference + public void setDefinitionsService(DefinitionsService definitionsService) { + this.definitionsService = definitionsService; } @Override - public long count(Condition condition, Map context, - ConditionESQueryBuilderDispatcher dispatcher) { - // Implement count logic if needed - return 0; + public boolean eval(Condition condition, Item item, Map context, + ConditionEvaluatorDispatcher dispatcher) { + // ... + return false; } } ---- -**OpenSearch Implementation:** +Registration examples for query builders: <<_builtin_condition_types,Plugin registration (Unomi 3.0+)>>. + +===== Query builders and the persistence split + +In the Apache Unomi distribution, **core** query builders for built-in conditions live in: + +* `persistence-elasticsearch/core` — `unomi-elasticsearch-conditions` feature +* `persistence-opensearch/core` — `unomi-opensearch-conditions` feature + +The base plugin bundle registers evaluators only; it does not register query builders. + +For **custom** conditions that participate in segment queries, ship search-engine-specific query builder bundles (see next section). + +===== Query builder implementation (Elasticsearch Java API Client) + [source,java] ---- -package org.apache.unomi.plugin.opensearch; +package com.example.plugin.elasticsearch; -import org.opensearch.client.opensearch._types.query_dsl.Query; +import co.elastic.clients.elasticsearch._types.query_dsl.Query; import org.apache.unomi.api.conditions.Condition; -import org.apache.unomi.persistence.opensearch.ConditionOSQueryBuilder; -import org.opensearch.index.query.QueryBuilder; -import org.opensearch.index.query.QueryBuilders; +import org.apache.unomi.persistence.elasticsearch.ConditionESQueryBuilder; +import org.apache.unomi.persistence.elasticsearch.ConditionESQueryBuilderDispatcher; -public class MyCustomQueryBuilder implements ConditionOSQueryBuilder { +import java.util.Map; + +public class MyCustomESQueryBuilder implements ConditionESQueryBuilder { @Override public Query buildQuery(Condition condition, Map context, - ConditionOSQueryBuilderDispatcher dispatcher) { - // Get parameters from the condition + ConditionESQueryBuilderDispatcher dispatcher) { String fieldName = (String) condition.getParameter("fieldName"); String fieldValue = (String) condition.getParameter("fieldValue"); - - // Build OpenSearch-specific query - return Query.of(q -> q - .bool(b -> b - .must(m -> m - .term(t -> t - .field(fieldName) - .value(v -> v.stringValue(fieldValue)) - ) - ) - ) - ); - } - - @Override - public long count(Condition condition, Map context, - ConditionOSQueryBuilderDispatcher dispatcher) { - // Implement count logic if needed - return 0; + return Query.of(q -> q.term(t -> t.field(fieldName).value(v -> v.stringValue(fieldValue)))); } } ---- -**OSGi Service Registration for ElasticSearch** (`elasticsearch-bundle/src/main/resources/OSGI-INF/blueprint/blueprint.xml`): -[source,xml] ----- - - - - - - - - - ----- +OpenSearch uses the same pattern with `org.apache.unomi.persistence.opensearch.ConditionOSQueryBuilder` and `org.opensearch.client.opensearch._types.query_dsl.Query`. -**OSGi Service Registration for OpenSearch** (`opensearch-bundle/src/main/resources/OSGI-INF/blueprint/blueprint.xml`): -[source,xml] +Register the query builder with Declarative Services in your Elasticsearch or OpenSearch bundle: + +[source,java] ---- - - - - - - - - - +@Component(service = ConditionESQueryBuilder.class, property = {"queryBuilderId=myCustomQueryBuilder"}) +public class MyCustomESQueryBuilder implements ConditionESQueryBuilder { + // buildQuery implementation ... +} ---- -**Condition Type Definition** (shared between both implementations): +Core persistence modules still contain legacy Blueprint registrations for built-in query builders; new plugin code should use DS as above. + +Shared condition type JSON: + [source,json] ---- { "metadata": { "id": "myCustomCondition", "name": "My Custom Condition", - "description": "A custom condition for demonstration purposes", - "systemTags": ["custom", "condition"], + "systemTags": ["custom", "condition", "profileCondition"], "readOnly": false }, + "conditionEvaluator": "myCustomConditionEvaluator", "queryBuilder": "myCustomQueryBuilder", "parameters": [ - { - "id": "fieldName", - "type": "string", - "multivalued": false - }, - { - "id": "fieldValue", - "type": "string", - "multivalued": false - } + { "id": "fieldName", "type": "string", "validation": { "required": true } }, + { "id": "fieldValue", "type": "string", "validation": { "required": true } } ] } ---- -==== Project Structure and Packaging - -For plugins that support both Elasticsearch and OpenSearch, follow this recommended project structure: - -``` -my-custom-plugin/ -├── pom.xml # Parent POM -├── my-custom-plugin-common/ # Shared code and interfaces -│ ├── pom.xml -│ └── src/main/java/ -│ └── org/apache/unomi/plugin/ -│ └── common/ -│ ├── MyCustomConditionType.java -│ └── MyCustomConditionEvaluator.java -├── my-custom-plugin-elasticsearch/ # Elasticsearch-specific implementation -│ ├── pom.xml -│ └── src/main/ -│ ├── java/ -│ │ └── org/apache/unomi/plugin/elasticsearch/ -│ │ └── MyCustomQueryBuilder.java -│ └── resources/ -│ └── OSGI-INF/blueprint/ -│ └── blueprint.xml -├── my-custom-plugin-opensearch/ # OpenSearch-specific implementation -│ ├── pom.xml -│ └── src/main/ -│ ├── java/ -│ │ └── org/apache/unomi/plugin/opensearch/ -│ │ └── MyCustomQueryBuilder.java -│ └── resources/ -│ └── OSGI-INF/blueprint/ -│ └── blueprint.xml -└── my-custom-plugin-features/ # Karaf feature definitions - ├── pom.xml - └── src/main/resources/ - └── features.xml -``` - -**Parent POM Configuration:** -[source,xml] +==== Multi-bundle layout for Elasticsearch and OpenSearch + +When your condition needs segment/query support on **both** search engines, use three Maven modules: + +[source] +---- +my-plugin/ +├── my-plugin-common/ # JSON descriptors, evaluators, shared code +├── my-plugin-elasticsearch/ # ConditionESQueryBuilder + ES mappings +├── my-plugin-opensearch/ # ConditionOSQueryBuilder + OS mappings +└── my-plugin-features/ # Karaf features.xml ---- - - org.apache.unomi.plugins - my-custom-plugin - 1.0.0 - pom - - - my-custom-plugin-common - my-custom-plugin-elasticsearch - my-custom-plugin-opensearch - my-custom-plugin-features - - - 3.1.0 - - +Common bundle dependencies: + +[source,xml] +---- + + org.apache.unomi + unomi-api + provided + + + org.apache.unomi + unomi-persistence-spi + provided + ---- -**Elasticsearch Bundle POM:** +Elasticsearch bundle adds: + [source,xml] ---- - - - org.apache.unomi.plugins - my-custom-plugin - 1.0.0 - - - my-custom-plugin-elasticsearch - bundle + + org.apache.unomi + unomi-persistence-elasticsearch-core + provided + +---- - - - org.apache.unomi - unomi-api - ${unomi.version} - - - org.apache.unomi - persistence-elasticsearch-core - ${unomi.version} - - - org.apache.unomi.plugins - my-custom-plugin-common - 1.0.0 - - +OpenSearch bundle adds: - - - - org.apache.felix - maven-bundle-plugin - true - - - my-custom-plugin-elasticsearch - My Custom Plugin - Elasticsearch - org.apache.unomi.plugin.elasticsearch - * - - - - - - +[source,xml] ---- + + org.apache.unomi + unomi-persistence-opensearch-core + provided + +---- + +===== Karaf features -**Karaf Feature Definitions:** +Define **separate** features per search engine. Use the same `queryBuilderId` in both implementations. -`features.xml` (single file with multiple feature definitions): [source,xml] ---- - - - - - - mvn:org.apache.unomi.plugins/my-custom-plugin-common/1.0.0 - mvn:org.apache.unomi.plugins/my-custom-plugin-elasticsearch/1.0.0 - unomi-elasticsearch + + + mvn:com.example/my-plugin-common/1.0.0 + mvn:com.example/my-plugin-elasticsearch/1.0.0 + unomi-elasticsearch-conditions - - - - mvn:org.apache.unomi.plugins/my-custom-plugin-common/1.0.0 - mvn:org.apache.unomi.plugins/my-custom-plugin-opensearch/1.0.0 - unomi-opensearch + + mvn:com.example/my-plugin-common/1.0.0 + mvn:com.example/my-plugin-opensearch/1.0.0 + unomi-opensearch-conditions ---- -==== Best Practices +Install at runtime: -1. **Naming Conventions** - - Use generic queryBuilder IDs (e.g., `myCustomQueryBuilder`) instead of search engine-specific ones - - Follow the new naming convention: remove "ES" references, use "QueryBuilder" suffix - - Keep the same queryBuilder ID across both implementations for consistency +* Elasticsearch: `feature:install my-plugin-elasticsearch` +* OpenSearch: `feature:install my-plugin-opensearch` -2. **Code Organization** - - Keep shared logic in a common module (condition types, evaluators, utilities) - - Implement search engine specific code only where necessary - - Use interfaces to define common behavior - - Minimize code duplication between implementations +[#_custom_distribution_feature] +===== Custom distribution feature -3. **OSGi Service Registration** - - Register services only in the appropriate bundle (Elasticsearch vs OpenSearch) - - Use the same `queryBuilderId` in service properties for both implementations - - Ensure proper dependency management to avoid conflicts +For production, add your plugin feature to a custom distribution based on `distribution/src/main/feature/feature.xml`. +Extend `unomi-distribution-elasticsearch` or `unomi-distribution-opensearch` and append your plugin feature **last**. -4. **Testing Strategy** - - Create test suites for both implementations - - Use Docker containers for integration testing: - ```yaml - services: - elasticsearch: - image: docker.elastic.co/elasticsearch/elasticsearch:9.15.0 - opensearch: - image: opensearchproject/opensearch:3.4.0 - ``` - - Test with both search engines to ensure compatibility +Elasticsearch example (abbreviated — compare with the real file in the repository): -5. **Deployment Configuration** - - Package search engine specific code in separate bundles - - Use a single feature file with multiple feature definitions for better organization - - Configure the appropriate feature in your deployment based on the selected search engine: - - For Elasticsearch: `feature:install my-custom-plugin-elasticsearch` - - For OpenSearch: `feature:install my-custom-plugin-opensearch` - - Document dependencies and requirements clearly +[source,xml] +---- + + unomi-distribution-elasticsearch + my-plugin-elasticsearch + +---- -[[_custom_distribution_feature]] -==== Custom Distribution Feature +OpenSearch deployments use `unomi-distribution-opensearch` and `unomi-healthcheck-opensearch` instead of the Elasticsearch healthcheck feature. -For production deployments, you can create a custom distribution's feature file to automatically include your plugin features in the startup configuration. This approach ensures your plugin is automatically deployed when Apache Unomi starts. +See also <<_building,Building and deploying>> and <<_configuration,Configuration>>. -**Creating a Custom Distribution Feature:** +==== Additional bundle resources -1. **Create the feature file** in a dedicated maven module. You can use the unomi-distribution module as a reference. +===== JSON Schemas for events -2. **Define your custom distribution** by extending the default ones: +Place event schemas under `META-INF/cxs/schemas/`. See <<_json_schemas,JSON Schemas>>. -[source,xml] ----- - - - unomi-base - unomi-startup - unomi-elasticsearch-core - unomi-persistence-core - unomi-services - unomi-rest-api - unomi-cxs-lists-extension - unomi-cxs-geonames-extension - unomi-cxs-privacy-extension - unomi-elasticsearch-conditions - unomi-plugins-base - unomi-plugins-request - unomi-plugins-mail - unomi-wab - unomi-web-tracker - unomi-healthcheck-elasticsearch - unomi-router-karaf-feature - unomi-groovy-actions - unomi-rest-ui - unomi-startup-complete - custom-feature - - ----- +===== Index mappings and Painless scripts + +* `META-INF/cxs/mappings/` — custom index mappings loaded by the persistence service +* `META-INF/cxs/painless/` — `.painless` scripts used during persistence operations + +===== Expression filters + +`META-INF/cxs/expressions/` — JSON descriptors for allowed scripting expressions (MVEL and related filters). + +[#_writing_plugins_cxs_patches] +===== CXS migration patch files + +`META-INF/cxs/patches/` — one-time structural updates. See <<_migration_patches,Migration patches>>. + +==== Custom value types and validation + +Register custom parameter types for condition validation by exposing a `ValueTypeValidator` OSGi service. +`DefinitionsService` forwards validators to `ConditionValidationService`. + +Use this when your condition type introduces a non-primitive `type` ID in parameter definitions. -**Key Points:** +==== Scheduling background work -- **Feature ordering**: Your custom plugin features should be added at the end of the feature list, after the core Apache Unomi features -- **Search engine specific**: Include only the appropriate feature for your selected search engine -- **Dependency management**: Ensure your plugin features are listed after their dependencies (e.g., after `unomi-elasticsearch-core` or `unomi-opensearch-core`) +Plugins can register `TaskExecutor` implementations and schedule tasks through `SchedulerService`. +See <<_scheduler,Task scheduler>> for REST APIs, shell commands, tenant-aware executors, and checkpoint patterns. +==== GraphQL provider extensions -**Benefits of Custom Distribution Feature:** +For GraphQL schema extensions, see `graphql/cxs-impl` and <<_graphql_api,GraphQL API>>. -- **Automatic deployment**: Your plugin is automatically installed when Apache Unomi starts -- **Consistent environments**: Ensures the same features are deployed across all environments -- **Production ready**: No manual feature installation required -- **Version control**: Configuration can be versioned and managed with your deployment +==== Reference plugins in the codebase -**Note**: For more detailed information about custom distributions, including environment-specific examples, see the <<_custom_distribution_feature,Custom Distribution Feature>> section or the <<_configuration,Configuration>> section of the documentation. +Use maintained modules in the main tree — not the older `samples/` demos (those predate DS migration and Blueprint removal). -+ -6. **Migration from Legacy Implementations** - - **DO NOT** use legacy mappings for custom query builders - - Rename existing query builders to follow new naming conventions - - Update condition type definitions to use new queryBuilder IDs - - Test thoroughly after migration +[cols="2,3", options="header"] +|=== +| Module | What to study -==== Learning from Core Implementation +| `plugins/advanced-conditions` | DS `ConditionEvaluator` + `pom.xml` with `_dsannotations` (start here for condition plugins) +| `plugins/kafka-injector` | DS lifecycle (`@Activate`, `@Deactivate`, `@Reference`) +| `extensions/lists-extension/actions` | Action type JSON + `AddToListsAction` logic +| `graphql/cxs-impl/.../actions` | DS `ActionExecutor` registrations +| `persistence-elasticsearch/core` | Query builder class implementations (study `querybuilders/core/`) +| `persistence-opensearch/core` | OpenSearch query builder counterparts +|=== + +The `samples/` directory holds historical integration demos (Twitter button, login page, etc.). +They illustrate end-to-end scenarios but are **not** maintained plugin templates — copy patterns from the modules above instead. + +Scenario walkthroughs that use samples: <<_samples,Samples>>. + +==== Best practices + +* Prefer Groovy actions or JSON-only bundles before writing Java when possible. +* Register OSGi services with **Declarative Services** (`@Component`); do not add new Blueprint XML. +* Import `unomi-bom`; use `provided` scope for Unomi artifacts inside Karaf. +* Use the same `queryBuilder` ID in JSON and in both ES/OS service registrations. +* Put evaluators and JSON descriptors in the **common** bundle; query builders in engine-specific bundles. +* Add `validation` metadata on condition parameters (see <<_condition_validation,Condition validation>>). +* Test segment conditions against both search engines if you ship both query builder bundles. +* Use `explain=true` on context requests to debug condition evaluation — see <<_debugging_conditions,Debugging conditions>>. +* Do not copy legacy `*ESQueryBuilder` class names from Unomi 2.x; follow the 3.0+ naming in <<_builtin_condition_types,Built-in condition types>>. + +===== Integration testing with Docker + +[source,yaml] +---- +services: + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:9.4.3 + opensearch: + image: opensearchproject/opensearch:3.4.0 +---- -You can study the core persistence implementations as references: +==== Reference implementations in the core codebase -1. ElasticSearch Implementation: - - Location: `persistence-elasticsearch/core` - - Key classes: - * ElasticSearchPersistenceServiceImpl - * ElasticSearchQueryBuilder +Study these modules when building persistence-related plugins: -2. OpenSearch Implementation: - - Location: `persistence-opensearch/core` - - Key classes: - * OpenSearchPersistenceServiceImpl - * OpenSearchQueryBuilder +[cols="2,3", options="header"] +|=== +| Area | Location -These implementations demonstrate: -- How to handle search engine specific features -- Query building patterns -- Mapping file organization -- Error handling and retry mechanisms -- Connection management -- Security configuration +| Elasticsearch persistence | `persistence-elasticsearch/core` +| OpenSearch persistence | `persistence-opensearch/core` +| Built-in condition evaluators (legacy Blueprint) | `plugins/baseplugin` — study evaluators only, not wiring +| Advanced evaluators (DS, preferred) | `plugins/advanced-conditions` +| Distribution features | `distribution/src/main/feature/feature.xml` +|=== +These show query building patterns, mapping layout, connection handling, and how built-in condition types are split between evaluators (plugins) and query builders (persistence modules). diff --git a/manual/src/theme/images/apache-unomi-logo.png b/manual/src/theme/images/apache-unomi-logo.png new file mode 100644 index 000000000..3da06424f Binary files /dev/null and b/manual/src/theme/images/apache-unomi-logo.png differ diff --git a/manual/src/theme/unomi-theme.yml b/manual/src/theme/unomi-theme.yml new file mode 100644 index 000000000..ff3bf2b7c --- /dev/null +++ b/manual/src/theme/unomi-theme.yml @@ -0,0 +1,284 @@ +## +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +## +font: + catalog: + #OpenSans + OpenSans: + normal: OpenSans-Regular.ttf + italic: OpenSans-Italic.ttf + bold: OpenSans-Bold.ttf + bold_italic: OpenSans-BoldItalic.ttf + #DroidSerif + DroidSerif: + normal: DroidSerif-Regular.ttf + italic: DroidSerif-Italic.ttf + bold: DroidSerif-Bold.ttf + bold_italic: DroidSerif-BoldItalic.ttf + +page: + background_color: #ffffff + layout: portrait + margin: [0.5in, 0.67in, 0.67in, 0.67in] + # margin_inner and margin_outer keys are used for recto/verso print margins when media=prepress + margin_inner: 0.75in + margin_outer: 0.59in + size: A4 +base: + align: justify + # color as hex string (leading # is optional) + font_color: #333333 + # color as RGB array + #font_color: [51, 51, 51] + # color as CMYK array (approximated) + #font_color: [0, 0, 0, 0.92] + #font_color: [0, 0, 0, 92%] + font_family: DroidSerif + # choose one of these font_size/line_height_length combinations + #font_size: 14 + #line_height_length: 20 + #font_size: 11.25 + #line_height_length: 18 + #font_size: 11.2 + #line_height_length: 16 + font_size: 10 + line_height_length: 15 + #font_size: 11.25 + #line_height_length: 18 + line_height: $base_line_height_length / $base_font_size + font_size_large: round($base_font_size * 1.25) + font_size_small: round($base_font_size * 0.85) + font_size_min: $base_font_size * 0.75 + font_style: normal + border_color: #cccccc + border_radius: 0 + border_width: 0.5 +# FIXME vertical_rhythm is weird; we should think in terms of ems +#vertical_rhythm: $base_line_height_length * 2 / 3 +# correct line height for Noto Serif metrics (comes with built-in line height) +vertical_rhythm: $base_line_height_length +horizontal_rhythm: $base_line_height_length +# QUESTION should vertical_spacing be block_spacing instead? +vertical_spacing: $vertical_rhythm +link: + font_color: #6d5ce7 +# literal is currently used for inline monospaced in prose and table cells +literal: + font_color: #6d5ce7 + font_family: DroidSerif +menu_caret_content: " \u203a " +heading: + align: left + font_color: #1e293b + #font_color: $base_font_color + font_family: OpenSans + font_style: bold + text_transform: none + # h1 is used for part titles (book doctype only) + h1_font_size: floor($base_font_size * 2.6) + # h2 is used for chapter titles (book doctype only) + h2_font_size: floor($base_font_size * 2.15) + h3_font_size: round($base_font_size * 1.7) + h4_font_size: $base_font_size_large + h5_font_size: $base_font_size + h6_font_size: $base_font_size_small + #line_height: 1.4 + # correct line height for Noto Serif metrics (comes with built-in line height) + line_height: 1 + margin_top: $vertical_rhythm * 0.4 + margin_bottom: $vertical_rhythm * 0.9 +title_page: + align: center + logo: + top: 8% + align: center + image: image:images/apache-unomi-logo.png[pdfwidth=50%,align=center] + title: + top: 42% + font_size: $heading_h1_font_size + font_family: $heading_font_family + font_style: bold + text_transform: none + font_color: $heading_font_color + line_height: 0.9 + subtitle: + font_size: $heading_h3_font_size + font_family: $heading_font_family + font_style: bold_italic + font_color: $heading_font_color + line_height: 1 + authors: + margin_top: $base_font_size * 1.25 + font_size: $base_font_size_large + font_color: $heading_font_color + font_family: $heading_font_family + revision: + margin_top: $base_font_size * 1.25 +block: + margin_top: 0 + margin_bottom: $vertical_rhythm +caption: + align: left + font_size: $base_font_size * 0.95 + font_style: italic + # FIXME perhaps set line_height instead of / in addition to margins? + margin_inside: $vertical_rhythm / 3 + #margin_inside: $vertical_rhythm / 4 + margin_outside: 0 +lead: + font_size: $base_font_size_large + line_height: 1.4 +abstract: + font_color: #5c6266 + font_size: $lead_font_size + line_height: $lead_line_height + font_style: italic + first_line_font_style: bold + title: + align: center + font_color: $heading_font_color + font_family: $heading_font_family + font_size: $heading_h4_font_size + font_style: $heading_font_style +admonition: + column_rule_color: $base_border_color + column_rule_width: $base_border_width + padding: [0, $horizontal_rhythm, 0, $horizontal_rhythm] + #icon: + # tip: + # name: fa-lightbulb-o + # stroke_color: 111111 + # size: 24 + label: + text_transform: uppercase + font_style: bold +blockquote: + font_color: $base_font_color + font_size: $base_font_size_large + border_color: $base_border_color + border_width: 5 + # FIXME disable negative padding bottom once margin collapsing is implemented + padding: [0, $horizontal_rhythm, $block_margin_bottom * -0.75, $horizontal_rhythm + $blockquote_border_width / 2] + cite_font_size: $base_font_size_small + cite_font_color: #999999 +# code is used for source blocks (perhaps change to source or listing?) +code: + font_color: #5c4cdb + font_family: $literal_font_family + font_size: ceil($base_font_size) + padding: $code_font_size + line_height: 1.25 + # line_gap is an experimental property to control how a background color is applied to an inline block element + line_gap: 3.8 + background_color: #f5f5f5 + border_color: #f5f5f5 + border_radius: $base_border_radius + border_width: 0.75 +conum: + font_family: OpenSans + font_color: $literal_font_color + font_size: $base_font_size + line_height: 4 / 3 +example: + border_color: #f5f5f5 + border_radius: $base_border_radius + border_width: 0.75 + background_color: #f5f5f5 + # FIXME reenable padding bottom once margin collapsing is implemented + padding: [$vertical_rhythm, $horizontal_rhythm, 0, $horizontal_rhythm] +image: + align: left +prose: + margin_top: $block_margin_top + margin_bottom: $block_margin_bottom +sidebar: + background_color: #eeeeee + border_color: #e1e1e1 + border_radius: $base_border_radius + border_width: $base_border_width + # FIXME reenable padding bottom once margin collapsing is implemented + padding: [$vertical_rhythm, $vertical_rhythm * 1.25, 0, $vertical_rhythm * 1.25] + title: + align: center + font_color: $heading_font_color + font_family: $heading_font_family + font_size: $heading_h4_font_size + font_style: $heading_font_style +thematic_break: + border_color: $base_border_color + border_style: solid + border_width: $base_border_width + margin_top: $vertical_rhythm * 0.5 + margin_bottom: $vertical_rhythm * 1.5 +description_list: + term_font_style: bold + term_spacing: $vertical_rhythm / 4 + description_indent: $horizontal_rhythm * 1.25 +outline_list: + indent: $horizontal_rhythm * 1.5 + #marker_font_color: 404040 + # NOTE outline_list_item_spacing applies to list items that do not have complex content + item_spacing: $vertical_rhythm / 2 +table: + background_color: #ffffff + font_color: $base_font_color + #head_background_color: + head_font_color: $base_font_color + head_font_style: bold + #body_background_color: + body_stripe_background_color: #f9f9f9 + foot_background_color: #f5f5f5 + border_color: #dddddd + border_width: $base_border_width + cell_padding: 3 +toc: + indent: $horizontal_rhythm + line_height: 1.4 + dot_leader: + #content: ". " + font_color: #a9a9a9 + #levels: 2 3 +# NOTE in addition to footer, header is also supported +footer: + font_size: $base_font_size_small + # NOTE if background_color is set, background and border will span width of page + border_color: #dddddd + border_width: 0.25 + height: $base_line_height_length * 2.5 + line_height: 1 + padding: [$base_line_height_length / 2, 1, 0, 1] + vertical_align: top + #image_vertical_align: or + # additional attributes for content: + # * {page-count} + # * {page-number} + # * {document-title} + # * {document-subtitle} + # * {chapter-title} + # * {section-title} + # * {section-or-chapter-title} + recto: + #columns: "<50% =0% >50%" + right: + #content: '{page-number}' + #content: '{section-or-chapter-title} - {page-number}' + content: '{document-title} - {page-number}' + #center: + # content: '{page-number}' + verso: + #columns: $footer_recto_columns + left: + content: $footer_recto_right_content + #content: '{page-number} - {chapter-title}' + #center: +# content: '{page-number}' \ No newline at end of file diff --git a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/CustomObjectMapper.java b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/CustomObjectMapper.java index c7d894e5d..a37c78ae0 100644 --- a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/CustomObjectMapper.java +++ b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/CustomObjectMapper.java @@ -62,10 +62,18 @@ public class CustomObjectMapper extends ObjectMapper { private PropertyTypedObjectDeserializer propertyTypedObjectDeserializer; private ItemDeserializer itemDeserializer; + /** + * Creates a mapper with default deserializers. + */ public CustomObjectMapper() { this(null); } + /** + * Creates a mapper with optional extra deserializers. + * + * @param deserializers additional deserializers to register + */ public CustomObjectMapper(Map> deserializers) { super(); super.registerModule(new JaxbAnnotationModule()); @@ -124,22 +132,49 @@ public CustomObjectMapper(Map> deserializers) { super.registerModule(deserializerModule); } + /** + * Registers a built-in item type for deserialization. + * + * @param itemType the item type identifier + * @param clazz the item class + */ public void registerBuiltInItemTypeClass(String itemType, Class clazz) { propertyTypedObjectDeserializer.registerMapping("itemType=" + itemType, clazz); itemDeserializer.registerMapping(itemType, clazz); } + /** + * Unregisters a built-in item type. + * + * @param itemType the item type identifier + */ public void unregisterBuiltInItemTypeClass(String itemType) { propertyTypedObjectDeserializer.unregisterMapping("itemType=" + itemType); itemDeserializer.unregisterMapping(itemType); } + /** + * Returns the shared {@link ObjectMapper} instance. + * + * @return the singleton mapper + */ public static ObjectMapper getObjectMapper() { return Holder.INSTANCE; } + /** + * Returns the shared {@link CustomObjectMapper} instance. + * + * @return the singleton custom mapper + */ public static CustomObjectMapper getCustomInstance() { return Holder.INSTANCE; } + /** + * Returns the class registered for a built-in item type. + * + * @param itemType the item type identifier + * @return the item class, or {@code null} + */ public Class getBuiltinItemTypeClass(String itemType) { return builtinItemTypeClasses.get(itemType); } diff --git a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/ItemDeserializer.java b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/ItemDeserializer.java index 4b8265e3e..68c8dbfef 100644 --- a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/ItemDeserializer.java +++ b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/ItemDeserializer.java @@ -31,19 +31,36 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +/** + * Jackson deserializer for {@link Item} subclasses keyed by {@code itemType}. + */ public class ItemDeserializer extends StdDeserializer { private static final long serialVersionUID = -7040054009670771266L; private Map> classes = new ConcurrentHashMap<>(); + /** + * Creates a deserializer for {@link Item}. + */ public ItemDeserializer() { super(Item.class); } + /** + * Registers an item type to class mapping. + * + * @param type the item type identifier + * @param clazz the item class + */ public void registerMapping(String type, Class clazz) { classes.put(type, clazz); } + /** + * Removes an item type mapping. + * + * @param type the item type identifier + */ public void unregisterMapping(String type) { classes.remove(type); } diff --git a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/PersistenceService.java b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/PersistenceService.java index 0f6ca0d3b..f9e457fc8 100644 --- a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/PersistenceService.java +++ b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/PersistenceService.java @@ -29,69 +29,61 @@ import java.util.Map; /** - * A service to provide persistence and retrieval of context server entities. + * Persistence SPI for storing and querying Unomi context-server entities. */ public interface PersistenceService { /** - * A unique name to identify the persistence service. - * @return a string containing the unique name for the persistence service. + * Returns the unique name of this persistence backend implementation. + * + * @return the persistence service name */ String getName(); /** - * Retrieves all known items of the specified class. - * WARNING: this method can be quite computationally intensive and calling the paged version {@link #getAllItems(Class, int, int, String)} is preferred. + * Loads all items of the given type. + * WARNING: this method can be expensive; prefer the paged overload {@link #getAllItems(Class, int, int, String)}. * - * @param the type of the {@link Item}s we want to retrieve - * @param clazz the {@link Item} subclass of entities we want to retrieve - * @return a list of all known items with the given type + * @param the item type + * @param clazz the {@link Item} subclass to load + * @return all known items of that type */ List getAllItems(Class clazz); /** - * Retrieves all known items of the specified class, ordered according to the specified {@code sortBy} String and and paged: only {@code size} of them are retrieved, - * starting with the {@code offset}-th one. + * Loads a paged slice of all items of the given type. *

- * TODO: use a Query object instead of distinct parameters? + * Future API versions may replace these parameters with a {@link org.apache.unomi.api.query.Query} object. * - * @param the type of the {@link Item}s we want to retrieve - * @param clazz the {@link Item} subclass of entities we want to retrieve - * @param offset zero or a positive integer specifying the position of the first item in the total ordered collection of matching items - * @param size a positive integer specifying how many matching items should be retrieved or {@code -1} if all of them should be retrieved - * @param sortBy an optional ({@code null} if no sorting is required) String of comma ({@code ,}) separated property names on which ordering should be performed, ordering - * elements according to the property order in the - * String, considering each in turn and moving on to the next one in case of equality of all preceding ones. Each property name is optionally followed by - * a column ({@code :}) and an order specifier: {@code asc} or {@code desc}. - * @return a {@link PartialList} of pages items with the given type + * @param the item type + * @param clazz the {@link Item} subclass to load + * @param offset zero-based index of the first result + * @param size maximum number of results to return, or {@code -1} for all matches + * @param sortBy optional comma-separated sort fields with optional {@code :asc} or {@code :desc} + * @return a paged list of items */ PartialList getAllItems(Class clazz, int offset, int size, String sortBy); /** - * Retrieves all known items of the specified class, ordered according to the specified {@code sortBy} String and and paged: only {@code size} of them are retrieved, - * starting with the {@code offset}-th one. + * Loads a paged slice of all items of the given type, optionally using a scroll query. *

- * TODO: use a Query object instead of distinct parameters? + * Future API versions may replace these parameters with a {@link org.apache.unomi.api.query.Query} object. * - * @param the type of the {@link Item}s we want to retrieve - * @param clazz the {@link Item} subclass of entities we want to retrieve - * @param offset zero or a positive integer specifying the position of the first item in the total ordered collection of matching items - * @param size a positive integer specifying how many matching items should be retrieved or {@code -1} if all of them should be retrieved - * @param sortBy an optional ({@code null} if no sorting is required) String of comma ({@code ,}) separated property names on which ordering should be performed, ordering - * elements according to the property order in the - * String, considering each in turn and moving on to the next one in case of equality of all preceding ones. Each property name is optionally followed by - * a column ({@code :}) and an order specifier: {@code asc} or {@code desc}. - * @param scrollTimeValidity the time the scrolling query should stay valid. This must contain a time unit value such as the ones supported by ElasticSearch, such as - * * the ones declared here : https://www.elastic.co/guide/en/elasticsearch/reference/current/common-options.html#time-units - * @return a {@link PartialList} of pages items with the given type + * @param the item type + * @param clazz the {@link Item} subclass to load + * @param offset zero-based index of the first result + * @param size maximum number of results to return, or {@code -1} for all matches + * @param sortBy optional comma-separated sort fields with optional {@code :asc} or {@code :desc} + * @param scrollTimeValidity scroll context lifetime, using Elasticsearch time-unit syntax + * @return a paged list of items, with scroll metadata when scrolling is enabled */ PartialList getAllItems(final Class clazz, int offset, int size, String sortBy, String scrollTimeValidity); /** - * Return true if the item which is saved in the persistence service is consistent + * Returns whether the item is consistent with the persistence backend schema. * - * @param item the item to the check if consistent - * @return {@code true} if the item is consistent, false otherwise + * @param item the item to validate + * @return {@code true} when the item is consistent */ boolean isConsistent(Item item); @@ -109,6 +101,7 @@ public interface PersistenceService { * @param item the item to persist * @param useBatching whether to use batching or not for saving the item. If activating there may be a delay between * the call to this method and the actual saving in the persistence backend. + * * @return {@code true} if the item was properly persisted, {@code false} otherwise */ boolean save(Item item, boolean useBatching); @@ -274,20 +267,20 @@ default boolean updateWithQueryAndStoredScript(Class clazz, String[] scripts, boolean updateWithQueryAndStoredScript(Date dateHint, Class clazz, String[] scripts, Map[] scriptParams, Condition[] conditions); /** - * Store script in the Database for later usage with updateWithQueryAndStoredScript function for example. + * Stores inline scripts in the persistence backend for later scripted updates. * - * @param scripts inline scripts map indexed by id - * @return {@code true} if the update was successful, {@code false} otherwise + * @param scripts inline scripts keyed by script ID + * @return {@code true} when all scripts are stored successfully */ boolean storeScripts(Map scripts); /** - * Retrieves the item identified with the specified identifier and with the specified Item subclass if it exists. + * Loads an item by ID and type. * - * @param the type of the Item subclass we want to retrieve - * @param itemId the identifier of the item we want to retrieve - * @param clazz the {@link Item} subclass of the item we want to retrieve - * @return the item identified with the specified identifier and with the specified Item subclass if it exists, {@code null} otherwise + * @param the item type + * @param itemId the item identifier + * @param clazz the {@link Item} subclass to load + * @return the item, or {@code null} when it does not exist */ T load(String itemId, Class clazz); @@ -298,11 +291,11 @@ default boolean updateWithQueryAndStoredScript(Class clazz, String[] scripts, T load(String itemId, Date dateHint, Class clazz); /** - * Load a custom item type identified by an identifier, an optional date hint and the identifier of the custom item type + * Loads a custom item by ID and custom item type. * - * @param itemId the identifier of the custom type we want to retrieve - * @param customItemType an identifier of the custom item type to load - * @return the CustomItem instance with the specified identifier and the custom item type if it exists, {@code null} otherwise + * @param itemId the custom item identifier + * @param customItemType the custom item type identifier + * @return the custom item, or {@code null} when it does not exist */ default CustomItem loadCustomItem(String itemId, String customItemType) { return loadCustomItem(itemId, null, customItemType); @@ -325,11 +318,11 @@ default CustomItem loadCustomItem(String itemId, String customItemType) { boolean remove(String itemId, Class clazz); /** - * Remove a custom item identified by the custom item identifier and the custom item type identifier + * Deletes a custom item by ID and custom item type. * - * @param itemId the identifier of the custom item to be removed - * @param customItemType the name of the custom item type - * @return {@code true} if the deletion was successful, {@code false} otherwise + * @param itemId the custom item identifier + * @param customItemType the custom item type identifier + * @return {@code true} when deletion succeeds */ boolean removeCustomItem(String itemId, String customItemType); @@ -344,45 +337,42 @@ default CustomItem loadCustomItem(String itemId, String customItemType) { boolean removeByQuery(Condition query, Class clazz); /** - * Retrieve the type mappings for a given itemType. This method queries the persistence service implementation - * to retrieve any type mappings it may have for the specified itemType. - *

- * This method may not return any results if the implementation doesn't support property type mappings + * Returns property mappings for the given item type, when supported by the backend. * - * @param itemType the itemType we want to retrieve the mappings for - * @return properties mapping + * @param itemType the item type name + * @return property name to mapping metadata */ Map> getPropertiesMapping(String itemType); /** - * Retrieve the mapping for one specific property for a given type. + * Returns the mapping metadata for one property on the given item type. * - * @param property the property name (can use nested dot notation) - * @param itemType the itemType we want to retrieve the mappings for - * @return property mapping + * @param property the property name, including nested dot notation + * @param itemType the item type name + * @return property mapping metadata */ Map getPropertyMapping(String property, String itemType); /** - * Create the persistence mapping for specific property for a given type. + * Creates or updates the persistence mapping for a property on the given item type. * - * @param property the PropertyType to create mapping for - * @param itemType the itemType we want to retrieve the mappings for + * @param property the property type definition + * @param itemType the item type name */ void setPropertyMapping(PropertyType property, String itemType); /** - * Create mapping + * Creates an index mapping from the given source definition. * - * @param type the type - * @param source the source + * @param type the item or index type + * @param source the mapping source definition */ void createMapping(String type, String source); /** * Checks whether the specified item satisfies the provided condition. *

- * TODO: rename to isMatching? + * The method name may change in a future release. * * @param query the condition we're testing the specified item against * @param item the item we're checking against the specified condition @@ -391,11 +381,11 @@ default CustomItem loadCustomItem(String itemId, String customItemType) { boolean testMatch(Condition query, Item item); /** - * validates if a condition throws exception at query build. + * Returns whether the condition can be compiled for the given item without error. * - * @param condition the condition we're testing the specified item against - * @param item the item we're checking against the specified condition - * @return {@code true} if the item satisfies the condition, {@code false} otherwise + * @param condition the condition to validate + * @param item the sample item used during validation + * @return {@code true} when the condition is valid */ boolean isValidCondition(Condition condition, Item item); @@ -416,7 +406,7 @@ default CustomItem loadCustomItem(String itemId, String customItemType) { List query(String fieldName, String fieldValue, String sortBy, Class clazz); /** - * Retrieves a list of items with the specified field having the specified values. + * Queries items whose field matches any of the given values. * * @param the type of the Item subclass we want to retrieve * @param fieldName the name of the field which we want items to have the specified values @@ -431,7 +421,7 @@ default CustomItem loadCustomItem(String itemId, String customItemType) { List query(String fieldName, String[] fieldValues, String sortBy, Class clazz); /** - * Retrieves a list of items with the specified field having the specified value. + * Queries items whose field matches the given value. * * @param the type of the Item subclass we want to retrieve * @param fieldName the name of the field which we want items to have the specified value @@ -448,7 +438,7 @@ default CustomItem loadCustomItem(String itemId, String customItemType) { PartialList query(String fieldName, String fieldValue, String sortBy, Class clazz, int offset, int size); /** - * Retrieves a list of items with the specified field having the specified value and having at least a field with the specified full text value in it, ordered according to the + * Queries items by field value and full-text match, with paging and optional sorting. * specified {@code sortBy} String and and paged: only {@code size} of them are retrieved, starting with the {@code offset}-th one. * * @param the type of the Item subclass we want to retrieve @@ -467,7 +457,7 @@ default CustomItem loadCustomItem(String itemId, String customItemType) { PartialList queryFullText(String fieldName, String fieldValue, String fulltext, String sortBy, Class clazz, int offset, int size); /** - * Retrieves a list of items having at least a field with the specified full text value in it, ordered according to the specified {@code sortBy} String and and paged: only + * Queries items that match a full-text search, with paging and optional sorting. * {@code size} of them are retrieved, starting with the {@code offset}-th one. * * @param the type of the Item subclass we want to retrieve @@ -499,7 +489,7 @@ default CustomItem loadCustomItem(String itemId, String customItemType) { List query(Condition query, String sortBy, Class clazz); /** - * Retrieves a list of items satisfying the specified {@link Condition}, ordered according to the specified {@code sortBy} String and and paged: only {@code size} of them + * Queries items that satisfy the condition, with paging and optional sorting. * are retrieved, starting with the {@code offset}-th one. * * @param the type of the Item subclass we want to retrieve @@ -516,7 +506,7 @@ default CustomItem loadCustomItem(String itemId, String customItemType) { PartialList query(Condition query, String sortBy, Class clazz, int offset, int size); /** - * Retrieves a list of items satisfying the specified {@link Condition}, ordered according to the specified {@code sortBy} String and and paged: only {@code size} of them + * Queries items that satisfy the condition, with paging and optional sorting. * are retrieved, starting with the {@code offset}-th one. If a scroll identifier and time validity are specified, they will be used to perform a scrolling query, meaning * that only partial results will be returned, but the scrolling can be continued. * @@ -532,6 +522,7 @@ default CustomItem loadCustomItem(String itemId, String customItemType) { * this will be used as the scrolling window size. * @param scrollTimeValidity the time the scrolling query should stay valid. This must contain a time unit value such as the ones supported by ElasticSearch, such as * the ones declared here : https://www.elastic.co/guide/en/elasticsearch/reference/current/common-options.html#time-units + * * @return a {@link PartialList} of items matching the specified criteria, with an scroll identifier and the scroll validity used if a scroll query was requested. */ PartialList query(Condition query, String sortBy, Class clazz, int offset, int size, String scrollTimeValidity); @@ -550,7 +541,7 @@ default CustomItem loadCustomItem(String itemId, String customItemType) { PartialList continueScrollQuery(Class clazz, String scrollIdentifier, String scrollTimeValidity); /** - * Retrieves a list of items satisfying the specified {@link Condition}, ordered according to the specified + * Queries items that satisfy the condition, with paging and optional * {@code sortBy} String and paged: only {@code size} of them are retrieved, starting with the * {@code offset}-th one. If a scroll identifier and time validity are specified, they will be used to perform a * scrolling query, meaning that only partial results will be returned, but the scrolling can be continued. @@ -566,6 +557,7 @@ default CustomItem loadCustomItem(String itemId, String customItemType) { * this will be used as the scrolling window size. * @param scrollTimeValidity the time the scrolling query should stay valid. This must contain a time unit value such as the ones supported by ElasticSearch, such as * the ones declared here : https://www.elastic.co/guide/en/elasticsearch/reference/current/common-options.html#time-units + * * @return a {@link PartialList} of items matching the specified criteria, with an scroll identifier and the scroll validity used if a scroll query was requested. */ PartialList queryCustomItem(Condition query, String sortBy, String customItemType, int offset, int size, String scrollTimeValidity); @@ -577,13 +569,14 @@ default CustomItem loadCustomItem(String itemId, String customItemType) { * @param scrollIdentifier a scroll identifier obtained by the execution of a first query and returned in the {@link PartialList} object * @param scrollTimeValidity a scroll time validity value for the scroll query to stay valid. This must contain a time unit value such as the ones supported by ElasticSearch, such as * the ones declared here : https://www.elastic.co/guide/en/elasticsearch/reference/current/common-options.html#time-units + * * @return a {@link PartialList} of items matching the specified criteria, with an scroll identifier and the scroll validity used if a scroll query was requested. Note that if * there are no more results the list will be empty but not null. */ PartialList continueCustomItemScrollQuery(String customItemType, String scrollIdentifier, String scrollTimeValidity); /** - * Retrieves the same items as {@code query(query, sortBy, clazz, 0, -1)} with the added constraints that the matching elements must also have at least a field matching the + * Queries items that satisfy the condition and a full-text filter. * specified full text query. * * @param the type of the Item subclass we want to retrieve @@ -601,7 +594,7 @@ default CustomItem loadCustomItem(String itemId, String customItemType) { PartialList queryFullText(String fulltext, Condition query, String sortBy, Class clazz, int offset, int size); /** - * Retrieves the number of items of the specified type as defined by the Item subclass public field {@code ITEM_TYPE} and matching the specified {@link Condition}. + * Counts items of the given type that match the condition. * * @param query the condition the items must satisfy * @param itemType the String representation of the item type we want to retrieve the count of, as defined by its class' {@code ITEM_TYPE} field @@ -611,7 +604,7 @@ default CustomItem loadCustomItem(String itemId, String customItemType) { long queryCount(Condition query, String itemType); /** - * Retrieves the number of items with the specified type as defined by the Item subclass public field {@code ITEM_TYPE}. + * Counts all items of the given type. * * @param itemType the String representation of the item type we want to retrieve the count of, as defined by its class' {@code ITEM_TYPE} field * @return the number of items of the specified type @@ -620,7 +613,7 @@ default CustomItem loadCustomItem(String itemId, String customItemType) { long getAllItemsCount(String itemType); /** - * Retrieves the number of items with the specified type as defined by the Item subclass public field {@code ITEM_TYPE} for the given tenant. + * Counts all items of the given type for the tenant. * * @param itemType the String representation of the item type we want to retrieve the count of, as defined by its class' {@code ITEM_TYPE} field * @param tenantId the ID of the tenant whose items should be counted @@ -630,7 +623,7 @@ default CustomItem loadCustomItem(String itemId, String customItemType) { long getAllItemsCount(String itemType, String tenantId); /** - * Retrieves the number of items with the specified type as defined by the Item subclass public field {@code ITEM_TYPE} matching the optional specified condition and + * Aggregates item counts for the given type, optionally filtered by condition and * aggregated according to the specified {@link BaseAggregate}. * Also return the global count of document matching the {@code ITEM_TYPE} * @@ -644,7 +637,7 @@ default CustomItem loadCustomItem(String itemId, String customItemType) { Map aggregateQuery(Condition filter, BaseAggregate aggregate, String itemType); /** - * Retrieves the number of items with the specified type as defined by the Item subclass public field {@code ITEM_TYPE} matching the optional specified condition and + * Aggregates item counts for the given type, optionally filtered by condition and * aggregated according to the specified {@link BaseAggregate}. * This aggregate won't return the global count and should therefore be much faster than {@link #aggregateQuery(Condition, BaseAggregate, String)} * @@ -656,7 +649,7 @@ default CustomItem loadCustomItem(String itemId, String customItemType) { Map aggregateWithOptimizedQuery(Condition filter, BaseAggregate aggregate, String itemType); /** - * Retrieves the number of items with the specified type as defined by the Item subclass public field {@code ITEM_TYPE} matching the optional specified condition and + * Aggregates item counts for the given type, optionally filtered by condition and * aggregated according to the specified {@link BaseAggregate}. * * @param filter the condition the items must match or {@code null} if no filtering is needed @@ -668,15 +661,15 @@ default CustomItem loadCustomItem(String itemId, String customItemType) { Map aggregateWithOptimizedQuery(Condition filter, BaseAggregate aggregate, String itemType, int size); /** - * Updates the persistence's engine indices if needed. + * Refreshes persistence engine indices when required by the backend. */ void refresh(); /** - * Updates the persistence's engine specific index. + * Refreshes the index for the item type represented by the given class. * - * @param clazz will use an index by class type - * @param a class that extends Item + * @param an {@link Item} subclass + * @param clazz the item class whose index should be refreshed */ default void refreshIndex(Class clazz) { refreshIndex(clazz, null); @@ -689,7 +682,7 @@ default void refreshIndex(Class clazz) { void refreshIndex(Class clazz, Date dateHint); /** - * deprecated: (use: purgeTimeBasedItems instead) + * @deprecated use {@link #purgeTimeBasedItems(int, Class)} instead */ @Deprecated void purge(Date date); @@ -698,13 +691,14 @@ default void refreshIndex(Class clazz) { * Purges time based data in the context server up to the specified days number of existence. * (This only works for time based data stored in rolling over indices, it have no effect on other types) * + * @param the item type * @param existsNumberOfDays the number of days * @param clazz the item type to be purged */ void purgeTimeBasedItems(int existsNumberOfDays, Class clazz); /** - * Retrieves all items of the specified Item subclass which specified ranged property is within the specified bounds, ordered according to the specified {@code sortBy} String + * Queries items whose ranged property falls within the given bounds, with paging and optional sorting. * and paged: only {@code size} of them are retrieved, starting with the {@code offset}-th one. *

* Both bounds are inclusive: items whose property value equals {@code from} or {@code to} are included in the results. Either bound may be {@code null} to leave that side @@ -725,7 +719,7 @@ default void refreshIndex(Class clazz) { PartialList rangeQuery(String fieldName, String from, String to, String sortBy, Class clazz, int offset, int size); /** - * Retrieves the specified metrics for the specified field of items of the specified type as defined by the Item subclass public field {@code ITEM_TYPE} and matching the + * Computes numeric metrics for a field on items that match the condition and type. * specified {@link Condition}. * * @param condition the condition the items must satisfy @@ -740,7 +734,7 @@ default void refreshIndex(Class clazz) { /** * Creates an index with for the specified item type in the persistence engine. *

- * TODO: remove from API? + * These low-level index operations may be removed from the public API in a future release. * * @param itemType the item type * @return {@code true} if the operation was successful, {@code false} otherwise @@ -750,7 +744,7 @@ default void refreshIndex(Class clazz) { /** * Removes the index for the specified item type. *

- * TODO: remove from API? + * These low-level index operations may be removed from the public API in a future release. * * @param itemType the item type * @return {@code true} if the operation was successful, {@code false} otherwise @@ -773,7 +767,7 @@ default void refreshIndex(Class clazz) { long calculateStorageSize(String tenantId); /** - * Retrieves the number of API calls made by a specific tenant. + * Returns the number of API calls recorded for the tenant. * * @param tenantId the ID of the tenant * @return the number of API calls diff --git a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/PropertyHelper.java b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/PropertyHelper.java index b6331c138..b2d02d6e8 100644 --- a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/PropertyHelper.java +++ b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/PropertyHelper.java @@ -29,13 +29,22 @@ import java.util.stream.Collectors; /** - * Helper method for properties + * Utility methods for reading and writing nested item properties. */ public class PropertyHelper { private static final Logger LOGGER = LoggerFactory.getLogger(PropertyHelper.class.getName()); private static DefaultResolver resolver = new DefaultResolver(); + /** + * Sets a nested property on the target using the given strategy. + * + * @param target the object to update + * @param propertyName the property path (dot-separated for nesting) + * @param propertyValue the value to set + * @param setPropertyStrategy the merge strategy ({@code alwaysSet}, {@code setIfMissing}, etc.) + * @return {@code true} if the target was modified + */ public static boolean setProperty(Object target, String propertyName, Object propertyValue, String setPropertyStrategy) { try { // Handle remove @@ -113,6 +122,12 @@ public static boolean setProperty(Object target, String propertyName, Object pro return false; } + /** + * Converts a value to a list (wraps non-list values). + * + * @param value the value to convert + * @return a list containing the value(s) + */ public static List convertToList(Object value) { List convertedList = new ArrayList<>(); if (value != null && value instanceof List) { @@ -123,6 +138,12 @@ public static List convertToList(Object value) { return convertedList; } + /** + * Coerces a value to an {@link Integer}, or returns {@code null}. + * + * @param value the value to coerce + * @return the integer value, or {@code null} + */ public static Integer getInteger(Object value) { if (value == null) { return null; @@ -138,6 +159,12 @@ public static Integer getInteger(Object value) { return null; } + /** + * Coerces a value to a {@link Long}, or returns {@code null}. + * + * @param value the value to coerce + * @return the long value, or {@code null} + */ public static Long getLong(Object value) { if (value instanceof Number) { return ((Number) value).longValue(); @@ -151,6 +178,12 @@ public static Long getLong(Object value) { return null; } + /** + * Coerces a value to a {@link Double}, or returns {@code null}. + * + * @param value the value to coerce + * @return the double value, or {@code null} + */ public static Double getDouble(Object value) { if (value instanceof Number) { return ((Number) value).doubleValue(); @@ -164,6 +197,12 @@ public static Double getDouble(Object value) { return null; } + /** + * Coerces a value to a {@link Boolean}. + * + * @param setPropertyValueBoolean the value to coerce + * @return the boolean value + */ public static Boolean getBooleanValue(Object setPropertyValueBoolean) { if (setPropertyValueBoolean instanceof Boolean) { @@ -185,6 +224,13 @@ public static Boolean getBooleanValue(Object setPropertyValueBoolean) { } + /** + * Coerces a property value according to a value type id. + * + * @param propertyValue the raw value + * @param valueTypeId the target type id ({@code boolean}, {@code integer}, etc.) + * @return the coerced value + */ public static Object getValueByTypeId(Object propertyValue, String valueTypeId) { if (("boolean".equals(valueTypeId))) { return getBooleanValue(propertyValue); @@ -195,6 +241,13 @@ public static Object getValueByTypeId(Object propertyValue, String valueTypeId) } } + /** + * Compares two property values with type-aware coercion. + * + * @param propertyValue the expected value + * @param beanPropertyValue the actual value on the bean + * @return {@code true} if the values are equal + */ public static boolean compareValues(Object propertyValue, Object beanPropertyValue) { if (propertyValue == null) { return true; @@ -212,6 +265,12 @@ public static boolean compareValues(Object propertyValue, Object beanPropertyVal } } + /** + * Flattens a nested map into dot-separated keys. + * + * @param in the map to flatten + * @return the flattened map + */ public static Map flatten(Map in) { return in.entrySet().stream() .filter(entry -> entry.getValue() != null) diff --git a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/PropertyTypedObjectDeserializer.java b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/PropertyTypedObjectDeserializer.java index 83f676bce..2993e5346 100644 --- a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/PropertyTypedObjectDeserializer.java +++ b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/PropertyTypedObjectDeserializer.java @@ -59,10 +59,22 @@ public class PropertyTypedObjectDeserializer extends UntypedObjectDeserializer { private Map> fieldValuesToMatch = new LinkedHashMap>(); + /** + * Creates a deserializer with optional list and map types. + * + * @param listType the list element type + * @param mapType the map value type + */ public PropertyTypedObjectDeserializer(JavaType listType, JavaType mapType) { super(listType, mapType); } + /** + * Registers a property match expression to a target class. + * + * @param matchExpression expression in {@code field=regex} form + * @param mappedClass the class to deserialize to + */ public void registerMapping(String matchExpression, Class mappedClass) { registry.put(matchExpression, mappedClass); @@ -75,6 +87,11 @@ public void registerMapping(String matchExpression, fieldValuesToMatch.put(fieldParts[0], valuesToMatch); } + /** + * Removes a property match expression mapping. + * + * @param matchExpression the expression to remove + */ public void unregisterMapping(String matchExpression) { registry.remove(matchExpression); String[] fieldParts = matchExpression.split("="); diff --git a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/aggregate/DateAggregate.java b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/aggregate/DateAggregate.java index 3ec6f1fcf..86d6b2655 100644 --- a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/aggregate/DateAggregate.java +++ b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/aggregate/DateAggregate.java @@ -89,20 +89,26 @@ public DateAggregate(String field, String interval, String format) { /** * Sets the interval; falls back to default when null/empty. + * + * @param interval the interval, in old or new format */ public void setInterval(String interval) { this.interval = (interval != null && !interval.isEmpty()) ? interval : DEFAULT_INTERVAL; } /** - * Returns the interval as it was originally defined + * Returns the interval as it was originally defined. + * + * @return the interval string */ public String getInterval() { return interval; } /** - * Returns the interval in the old format (1M, 1d, etc.) + * Returns the interval in the old format (1M, 1d, etc.). + * + * @return the interval in old format */ public String getIntervalInOldFormat() { if (isOldFormat(interval)) { @@ -112,7 +118,9 @@ public String getIntervalInOldFormat() { } /** - * Returns the interval in the new format (Month, Day, etc.) + * Returns the interval in the new format (Month, Day, etc.). + * + * @return the interval in new format */ public String getIntervalInNewFormat() { if (isNewFormat(interval)) { @@ -134,28 +142,40 @@ public String getIntervalByAlias(String alias) { } /** - * Determines if the interval uses the old format + * Determines if the interval uses the old format. + * + * @param value the interval string to check + * @return {@code true} if the value uses old format */ public boolean isOldFormat(String value) { return OLD_TO_NEW_FORMAT.containsKey(value); } /** - * Determines if the interval uses the new format + * Determines if the interval uses the new format. + * + * @param value the interval string to check + * @return {@code true} if the value uses new format */ public boolean isNewFormat(String value) { return NEW_TO_OLD_FORMAT.containsKey(value); } /** - * Converts from old format to new format + * Converts from old format to new format. + * + * @param oldFormat the interval in old format + * @return the interval in new format */ public static String convertToNewFormat(String oldFormat) { return OLD_TO_NEW_FORMAT.getOrDefault(oldFormat, oldFormat); } /** - * Converts from new format to old format + * Converts from new format to old format. + * + * @param newFormat the interval in new format + * @return the interval in old format */ public static String convertToOldFormat(String newFormat) { return NEW_TO_OLD_FORMAT.getOrDefault(newFormat, newFormat); @@ -163,6 +183,8 @@ public static String convertToOldFormat(String newFormat) { /** * Returns the output format, if any. + * + * @return the output format, or {@code null} */ public String getFormat() { return format; @@ -170,6 +192,8 @@ public String getFormat() { /** * Sets the output format. + * + * @param format the output format */ public void setFormat(String format) { this.format = format; diff --git a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/aggregate/DateRangeAggregate.java b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/aggregate/DateRangeAggregate.java index 74d51c98a..cf7e1bb06 100644 --- a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/aggregate/DateRangeAggregate.java +++ b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/aggregate/DateRangeAggregate.java @@ -45,6 +45,8 @@ public DateRangeAggregate(String field, String format, List dateRange /** * Returns the configured date ranges. + * + * @return the date ranges */ public List getDateRanges() { return dateRanges; @@ -52,6 +54,8 @@ public List getDateRanges() { /** * Sets the date ranges to use for bucketing. + * + * @param dateRanges the date ranges */ public void setDateRanges(List dateRanges) { this.dateRanges = dateRanges; @@ -59,6 +63,8 @@ public void setDateRanges(List dateRanges) { /** * Returns the date format, if any. + * + * @return the date format, or {@code null} */ public String getFormat() { return format; @@ -66,6 +72,8 @@ public String getFormat() { /** * Sets the date format. + * + * @param format the date format */ public void setFormat(String format) { this.format = format; diff --git a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/aggregate/IpRangeAggregate.java b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/aggregate/IpRangeAggregate.java index 2793c3d9b..0d98c2262 100644 --- a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/aggregate/IpRangeAggregate.java +++ b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/aggregate/IpRangeAggregate.java @@ -40,6 +40,8 @@ public IpRangeAggregate(String field, List ranges) { /** * Returns the configured IP ranges. + * + * @return the IP ranges */ public List getRanges() { return ranges; @@ -47,6 +49,8 @@ public List getRanges() { /** * Sets the IP ranges to use for bucketing. + * + * @param ranges the IP ranges */ public void setRanges(List ranges) { this.ranges = ranges; diff --git a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/aggregate/NumericRangeAggregate.java b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/aggregate/NumericRangeAggregate.java index fcb6f3546..1f7d82fb0 100644 --- a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/aggregate/NumericRangeAggregate.java +++ b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/aggregate/NumericRangeAggregate.java @@ -40,6 +40,8 @@ public NumericRangeAggregate(String field, List ranges) { /** * Returns the configured numeric ranges. + * + * @return the numeric ranges */ public List getRanges() { return ranges; @@ -47,6 +49,8 @@ public List getRanges() { /** * Sets the numeric ranges to use for bucketing. + * + * @param ranges the numeric ranges */ public void setRanges(List ranges) { this.ranges = ranges; diff --git a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/aggregate/TermsAggregate.java b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/aggregate/TermsAggregate.java index 9a71f20bd..d8998721f 100644 --- a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/aggregate/TermsAggregate.java +++ b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/aggregate/TermsAggregate.java @@ -31,11 +31,22 @@ public class TermsAggregate extends BaseAggregate{ */ private int numPartitions = -1; - + /** + * Creates a terms aggregation on the given field. + * + * @param field the field to aggregate on + */ public TermsAggregate(String field) { super(field); } + /** + * Creates a partitioned terms aggregation. + * + * @param field the field to aggregate on + * @param partition the zero-based partition index + * @param numPartitions the total number of partitions + */ public TermsAggregate(String field, int partition, int numPartitions) { super(field); this.partition = partition; @@ -44,6 +55,8 @@ public TermsAggregate(String field, int partition, int numPartitions) { /** * Returns the zero-based partition index, or {@code -1} if partitioning is disabled. + * + * @return the partition index */ public int getPartition() { return partition; @@ -51,6 +64,8 @@ public int getPartition() { /** * Returns the total number of partitions, or {@code -1} if partitioning is disabled. + * + * @return the number of partitions */ public int getNumPartitions() { return numPartitions; 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 86a8aebe4..1c0235b70 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 @@ -40,6 +40,9 @@ import java.util.*; import java.util.stream.Collectors; +/** + * Resolves parameter references and script expressions in condition parameter values. + */ public class ConditionContextHelper { private static final Logger LOGGER = LoggerFactory.getLogger(ConditionContextHelper.class); @@ -115,6 +118,14 @@ private static void loadMappingFile() throws IOException { } } + /** + * Resolves parameter references and script expressions without type validation. + * + * @param condition the condition to resolve + * @param context context map for parameter resolution + * @param scriptExecutor executor for script expressions + * @return the resolved condition, or {@code null} on failure + */ public static Condition getContextualCondition(Condition condition, Map context, ScriptExecutor scriptExecutor) { return getContextualCondition(condition, context, scriptExecutor, false, null, null); } @@ -128,6 +139,7 @@ public static Condition getContextualCondition(Condition condition, Map forceFoldToASCII(Collection collection) { if (collection != null) { return collection.stream().map(ConditionContextHelper::forceFoldToASCII).collect(Collectors.toList()); @@ -684,6 +709,12 @@ public static Collection forceFoldToASCII(Collection collection) { return null; } + /** + * Folds each string in an array to ASCII in place. + * + * @param s the string array to fold + * @return the same array with folded elements + */ public static String[] foldToASCII(String[] s) { if (s != null) { for (int i = 0; i < s.length; i++) { @@ -693,6 +724,12 @@ public static String[] foldToASCII(String[] s) { return s; } + /** + * Folds a string to ASCII using the built-in character mapping. + * + * @param s the string to fold + * @return the folded string, or {@code null} + */ public static String foldToASCII(String s) { if (s == null) { return null; @@ -715,6 +752,13 @@ public static String foldToASCII(String s) { return result.toString(); } + /** + * Folds string elements in a collection to ASCII. + * + * @param the collection element type + * @param s the collection to fold + * @return a new collection with folded strings, or {@code null} + */ public static Collection foldToASCII(Collection s) { if (s != null) { return s.stream().map(o -> { diff --git a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/ValueTypeValidatorRegistry.java b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/ValueTypeValidatorRegistry.java index 31818b394..668a1a63f 100644 --- a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/ValueTypeValidatorRegistry.java +++ b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/ValueTypeValidatorRegistry.java @@ -40,6 +40,9 @@ public class ValueTypeValidatorRegistry { private final Map validatorsByTypeId = new ConcurrentHashMap<>(); + /** + * Creates the registry and sets the static instance. + */ public ValueTypeValidatorRegistry() { instance = this; } @@ -58,11 +61,21 @@ public static Map getValidators() { return registry.validatorsByTypeId; } + /** + * OSGi bind callback for value type validators. + * + * @param validator the validator to register + */ @Reference(service = ValueTypeValidator.class, cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) public void bindValidator(ValueTypeValidator validator) { validatorsByTypeId.put(validator.getValueTypeId().toLowerCase(Locale.ROOT), validator); } + /** + * OSGi unbind callback for value type validators. + * + * @param validator the validator being removed + */ public void unbindValidator(ValueTypeValidator validator) { validatorsByTypeId.remove(validator.getValueTypeId().toLowerCase(Locale.ROOT)); } diff --git a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/datemath/DateMathParseException.java b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/datemath/DateMathParseException.java index f89e44e73..528a9b47c 100644 --- a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/datemath/DateMathParseException.java +++ b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/datemath/DateMathParseException.java @@ -23,18 +23,42 @@ * the same semantics without a direct dependency on Elasticsearch. */ public class DateMathParseException extends RuntimeException { + /** + * Creates an exception with the given message. + * + * @param message the detail message + */ public DateMathParseException(String message) { super(message); } + /** + * Creates an exception with message and cause. + * + * @param message the detail message + * @param cause the cause + */ public DateMathParseException(String message, Throwable cause) { super(message, cause); } + /** + * Creates an exception with a formatted message. + * + * @param message the message format string + * @param args format arguments + */ public DateMathParseException(String message, Object... args) { super(String.format(message, args)); } + /** + * Creates an exception with formatted message and cause. + * + * @param message the message format string + * @param cause the cause + * @param args format arguments + */ public DateMathParseException(String message, Throwable cause, Object... args) { super(String.format(message, args), cause); } diff --git a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/datemath/DateMathParser.java b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/datemath/DateMathParser.java index 46fb505b1..85152d849 100644 --- a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/datemath/DateMathParser.java +++ b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/datemath/DateMathParser.java @@ -37,11 +37,26 @@ */ public class DateMathParser { + /** + * Returns {@code true} if the sequence is null or empty. + * + * @param cs the character sequence + * @return {@code true} when null or empty + */ public static boolean isNullOrEmpty(CharSequence cs) { return cs == null || cs.length() == 0; } + /** + * Helpers for converting parsed temporal accessors to zoned date-times. + */ public static class DateFormatters { + /** + * Converts a temporal accessor to a {@link ZonedDateTime} in UTC when no zone is present. + * + * @param accessor the parsed temporal accessor + * @return the zoned date-time + */ public static ZonedDateTime from(TemporalAccessor accessor) { // Default to UTC if no zone or offset is present ZoneId zone = accessor.query(TemporalQueries.zone()); @@ -80,6 +95,12 @@ public static ZonedDateTime from(TemporalAccessor accessor) { private final JavaDateFormatter formatter; private final DateTimeFormatter roundUpFormatter; + /** + * Creates a parser with the given formatters. + * + * @param formatter the primary date formatter + * @param roundUpFormatter formatter used when rounding up date-only values + */ public DateMathParser(JavaDateFormatter formatter, DateTimeFormatter roundUpFormatter) { this.formatter = formatter; this.roundUpFormatter = roundUpFormatter; @@ -94,6 +115,15 @@ private String normalizeDateMathInput(String input) { return input; } + /** + * Parses a date math expression into an instant. + * + * @param text the date math expression + * @param now supplier for the current epoch millis (used for {@code now}) + * @param roundUpProperty whether to round up when rounding + * @param timeZone the time zone for parsing + * @return the parsed instant + */ public Instant parse(String text, LongSupplier now, boolean roundUpProperty, ZoneId timeZone) { text = text.trim(); diff --git a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/datemath/JavaDateFormatter.java b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/datemath/JavaDateFormatter.java index 2cb3fae7d..52f314711 100644 --- a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/datemath/JavaDateFormatter.java +++ b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/datemath/JavaDateFormatter.java @@ -40,6 +40,11 @@ public class JavaDateFormatter { private final boolean allowEpochMillis; private final boolean allowEpochSecond; + /** + * Creates a formatter supporting one or more pipe-separated patterns. + * + * @param formatString the format string (e.g. {@code strict_date_optional_time||epoch_millis}) + */ public JavaDateFormatter(String formatString) { this.formats = new ArrayList<>(); boolean epochMillis = false; @@ -72,6 +77,12 @@ private String adjustForCaseInsensitive(String input) { return input; } + /** + * Parses an input string using the configured formats. + * + * @param input the date/time string + * @return the parsed temporal accessor + */ public TemporalAccessor parse(String input) { // Numeric check if (isNumeric(input)) { @@ -331,11 +342,21 @@ private FormatDefinition fmt(String name, String pattern) { return new FormatDefinition(name, pattern, dtf); } + /** + * A named date format pattern with its compiled formatter. + */ public static class FormatDefinition { final String name; final String pattern; final DateTimeFormatter formatter; + /** + * Creates a format definition. + * + * @param name the format name + * @param pattern the pattern string + * @param formatter the compiled formatter + */ public FormatDefinition(String name, String pattern, DateTimeFormatter formatter) { this.name = name; this.pattern = pattern; diff --git a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/dispatcher/ConditionQueryBuilderDispatcher.java b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/dispatcher/ConditionQueryBuilderDispatcher.java index 95c3f2a40..b540c0e46 100644 --- a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/dispatcher/ConditionQueryBuilderDispatcher.java +++ b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/dispatcher/ConditionQueryBuilderDispatcher.java @@ -70,7 +70,11 @@ private String resolveLegacyQueryBuilderId(String queryBuilderId, String conditi /** * Resolves the final queryBuilder key to use, trying the provided key first, then applying legacy mapping. - * The {@code hasBuilder} predicate is used to test the presence of a builder for a given key. + * + * @param queryBuilderKey the requested query builder key + * @param conditionTypeId the condition type id (for deprecation logging) + * @param hasBuilder predicate that tests whether a builder exists for a key + * @return the resolved key, or {@code null} if none found */ public String findQueryBuilderKey(String queryBuilderKey, String conditionTypeId, Predicate hasBuilder) { diff --git a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/evaluator/ConditionEvaluator.java b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/evaluator/ConditionEvaluator.java index fe2cf0451..76fe2478c 100644 --- a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/evaluator/ConditionEvaluator.java +++ b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/evaluator/ConditionEvaluator.java @@ -27,6 +27,15 @@ */ public interface ConditionEvaluator { + /** + * Evaluates a condition against an item. + * + * @param condition the condition to evaluate + * @param item the item to test + * @param context evaluation context + * @param dispatcher dispatcher for nested condition evaluation + * @return {@code true} if the condition matches + */ boolean eval(Condition condition, Item item, Map context, ConditionEvaluatorDispatcher dispatcher); } diff --git a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/evaluator/impl/ConditionEvaluatorDispatcherImpl.java b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/evaluator/impl/ConditionEvaluatorDispatcherImpl.java index d4be6b6a0..4fefdff52 100644 --- a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/evaluator/impl/ConditionEvaluatorDispatcherImpl.java +++ b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/evaluator/impl/ConditionEvaluatorDispatcherImpl.java @@ -57,37 +57,77 @@ public class ConditionEvaluatorDispatcherImpl private TracerService tracerService; private DefinitionsService definitionsService; + /** + * Default constructor. + */ public ConditionEvaluatorDispatcherImpl() { } + /** + * OSGi reference setter for the metrics service. + * + * @param metricsService the metrics service + */ @Reference public void setMetricsService(MetricsService metricsService) { this.metricsService = metricsService; } + /** + * OSGi reference setter for the script executor. + * + * @param scriptExecutor the script executor + */ @Reference public void setScriptExecutor(ScriptExecutor scriptExecutor) { this.scriptExecutor = scriptExecutor; } + /** + * OSGi reference setter for the definitions service. + * + * @param definitionsService the definitions service + */ @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC) public void setDefinitionsService(DefinitionsService definitionsService) { this.definitionsService = definitionsService; } + /** + * OSGi reference unsetter for the definitions service. + * + * @param definitionsService the definitions service being removed + */ public void unsetDefinitionsService(DefinitionsService definitionsService) { this.definitionsService = null; } + /** + * OSGi bind callback for condition evaluators. + * + * @param evaluator the evaluator implementation + * @param props service properties including {@code conditionEvaluatorId} + */ @Reference(service = ConditionEvaluator.class, cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) public void bindEvaluator(ConditionEvaluator evaluator, Map props) { evaluators.put((String) props.get("conditionEvaluatorId"), evaluator); } + /** + * OSGi unbind callback for condition evaluators. + * + * @param evaluator the evaluator being removed + * @param props service properties including {@code conditionEvaluatorId} + */ public void unbindEvaluator(ConditionEvaluator evaluator, Map props) { evaluators.remove((String) props.get("conditionEvaluatorId")); } + /** + * OSGi reference setter for the tracer service. + * + * @param tracerService the tracer service + */ @Reference public void setTracerService(TracerService tracerService) { this.tracerService = tracerService; diff --git a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/geo/DistanceUnit.java b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/geo/DistanceUnit.java index ab6f4f97b..ab7cd77e9 100644 --- a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/geo/DistanceUnit.java +++ b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/geo/DistanceUnit.java @@ -62,34 +62,82 @@ public enum DistanceUnit { this.aliases = aliases; } + /** + * Returns the Earth's circumference in this unit. + * + * @return the circumference + */ public double getEarthCircumference() { return EARTH_EQUATOR / metersPerUnit; } + /** + * Returns the Earth's radius in this unit. + * + * @return the radius + */ public double getEarthRadius() { return EARTH_SEMI_MAJOR_AXIS / metersPerUnit; } + /** + * Returns the distance per degree of latitude in this unit. + * + * @return the distance per degree + */ public double getDistancePerDegree() { return EARTH_EQUATOR / (360.0 * metersPerUnit); } + /** + * Converts a value in this unit to meters. + * + * @param value the value in this unit + * @return the value in meters + */ public double toMeters(double value) { return value * metersPerUnit; } + /** + * Converts a value from meters to this unit. + * + * @param value the value in meters + * @return the value in this unit + */ public double fromMeters(double value) { return value / metersPerUnit; } + /** + * Converts a value from this unit to another unit. + * + * @param value the value in this unit + * @param toUnit the target unit + * @return the converted value + */ public double convert(double value, DistanceUnit toUnit) { return (value * metersPerUnit) / toUnit.metersPerUnit; } + /** + * Converts a value between two distance units. + * + * @param value the value to convert + * @param from the source unit + * @param to the target unit + * @return the converted value + */ public static double convert(double value, DistanceUnit from, DistanceUnit to) { return (value * from.metersPerUnit) / to.metersPerUnit; } + /** + * Parses a distance unit from its string representation. + * + * @param unit the unit name or alias + * @return the matching distance unit + */ public static DistanceUnit fromString(String unit) { if (unit == null || unit.isEmpty()) { throw new IllegalArgumentException("Unit string must not be null or empty"); @@ -101,6 +149,13 @@ public static DistanceUnit fromString(String unit) { return distanceUnit; } + /** + * Parses the unit suffix from a distance string. + * + * @param distance the distance string (e.g. {@code 10km}) + * @param defaultUnit the unit to use when no suffix is found + * @return the parsed distance unit + */ public static DistanceUnit parseUnit(String distance, DistanceUnit defaultUnit) { for (DistanceUnit unit : values()) { for (String alias : unit.aliases) { @@ -112,6 +167,13 @@ public static DistanceUnit parseUnit(String distance, DistanceUnit defaultUnit) return defaultUnit; } + /** + * Parses a distance string and returns the value in this unit. + * + * @param distance the distance string (e.g. {@code 10km}) + * @param defaultUnit the unit to use when no suffix is found + * @return the distance in this unit + */ public double parse(String distance, DistanceUnit defaultUnit) { Distance parsed = Distance.parseDistance(distance, defaultUnit); return convert(parsed.value, parsed.unit, this); @@ -122,15 +184,30 @@ public String toString() { return aliases[0]; } + /** + * A distance value paired with its unit. + */ public static class Distance { public final double value; public final DistanceUnit unit; + /** + * Creates a distance value. + * + * @param value the numeric distance + * @param unit the distance unit + */ public Distance(double value, DistanceUnit unit) { this.value = value; this.unit = unit; } + /** + * Converts this distance to another unit. + * + * @param toUnit the target unit + * @return a new distance in the target unit + */ public Distance convert(DistanceUnit toUnit) { double convertedValue = DistanceUnit.convert(value, unit, toUnit); return new Distance(convertedValue, toUnit); @@ -154,10 +231,23 @@ public String toString() { return value + " " + unit.toString(); } + /** + * Parses a distance string using meters as the default unit. + * + * @param distance the distance string + * @return the parsed distance + */ public static Distance parseDistance(String distance) { return parseDistance(distance, DistanceUnit.METERS); } + /** + * Parses a distance string with a default unit. + * + * @param distance the distance string + * @param defaultUnit the unit when no suffix is found + * @return the parsed distance + */ public static Distance parseDistance(String distance, DistanceUnit defaultUnit) { for (DistanceUnit unit : values()) { for (String alias : unit.aliases) { diff --git a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/geo/GeoDistance.java b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/geo/GeoDistance.java index 36b068066..2270bcd8a 100644 --- a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/geo/GeoDistance.java +++ b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/geo/GeoDistance.java @@ -76,6 +76,12 @@ public double calculate(double lat1, double lon1, double lat2, double lon2, Dist public static final double TO_RADIANS = Math.PI / 180D; public static final double TO_DEGREES = 180D / Math.PI; + /** + * Converts degrees to radians. + * + * @param degrees the angle in degrees + * @return the angle in radians + */ public static double toRadians(double degrees) { return degrees * TO_RADIANS; } diff --git a/pom.xml b/pom.xml index 02bd8aa79..00b844bdb 100644 --- a/pom.xml +++ b/pom.xml @@ -165,6 +165,7 @@ 1.7 3.2.2 2.13 + 3.6.0 2.0.0 1.0.6 @@ -567,6 +568,37 @@ + + javadoc-tags-warn + + false + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${maven-checkstyle-javadoc-plugin.version} + + + com.puppycrawl.tools + checkstyle + 10.21.4 + + + + config/checkstyle-javadoc.xml + true + false + false + true + warning + + + + + + update-licenses @@ -880,6 +912,16 @@ org.apache.maven.plugins maven-javadoc-plugin ${maven-javadoc.plugin.version} + + + protected + + all,-missing + true + + true + false + org.apache.maven.plugins diff --git a/rest/README.md b/rest/README.md index b44c62130..2c9d4288a 100644 --- a/rest/README.md +++ b/rest/README.md @@ -15,10 +15,32 @@ ~ limitations under the License. --> -# How to generate the REST API documentation +# Apache Unomi REST API -- Switch to the `rest-documentation` branch, a different branch is need so that we can add the proper `@Path` annotations on the endpoint and add the Maven plugin configuration -for documentation generation. -- Make sure that the changes from master are incorporated to the branch by performing a rebase: `git rebase master`. +## Consumer documentation + +The REST API is served under `/cxs` on the default HTTP port (8181). + +Unomi 3.1 authentication: + +| Access | Credentials | +|--------|-------------| +| Public endpoints (`/cxs/context.json`, `/cxs/eventcollector`, …) | `X-Unomi-Api-Key: ` | +| Tenant private endpoints | Basic auth `tenantId:privateApiKey` | +| System administration (tenants, cluster, tasks, …) | JAAS user (for example `karaf:karaf`) | + +Key manual chapters: + +* `manual/src/main/asciidoc/multitenancy.adoc` — tenants and API keys +* `manual/src/main/asciidoc/request-examples.adoc` — curl examples with auth +* `manual/src/main/asciidoc/useful-unomi-urls.adoc` — endpoint reference +* `manual/src/main/asciidoc/scheduler.adoc` — `/cxs/tasks` API + +Postman collections: see `rest/postman-readme.md`. + +## Generating Miredot API documentation (maintainers) + +- Switch to the `rest-documentation` branch (adds `@Path` annotations and the Maven plugin for doc generation). +- Rebase on master: `git rebase master`. - Run `mvn test`. -- The documentation should now be available via `target/miredot/index.html` \ No newline at end of file +- Open `target/miredot/index.html`. diff --git a/rest/src/main/java/org/apache/unomi/rest/authentication/AuthenticationFilter.java b/rest/src/main/java/org/apache/unomi/rest/authentication/AuthenticationFilter.java index df37dbf55..b4db2754e 100644 --- a/rest/src/main/java/org/apache/unomi/rest/authentication/AuthenticationFilter.java +++ b/rest/src/main/java/org/apache/unomi/rest/authentication/AuthenticationFilter.java @@ -88,6 +88,14 @@ public class AuthenticationFilter implements ContainerRequestFilter { private final SecurityService securityService; private final ExecutionContextManager executionContextManager; + /** + * Creates the authentication filter. + * + * @param restAuthenticationConfig the REST authentication configuration + * @param tenantService the tenant service + * @param securityService the security service + * @param executionContextManager the execution context manager + */ public AuthenticationFilter(RestAuthenticationConfig restAuthenticationConfig, TenantService tenantService, SecurityService securityService, diff --git a/rest/src/main/java/org/apache/unomi/rest/authentication/AuthorizingInterceptor.java b/rest/src/main/java/org/apache/unomi/rest/authentication/AuthorizingInterceptor.java index 39664a755..a6b5b46c1 100644 --- a/rest/src/main/java/org/apache/unomi/rest/authentication/AuthorizingInterceptor.java +++ b/rest/src/main/java/org/apache/unomi/rest/authentication/AuthorizingInterceptor.java @@ -26,6 +26,11 @@ */ public class AuthorizingInterceptor extends SimpleAuthorizingInterceptor { + /** + * Creates the interceptor using the REST authentication configuration. + * + * @param restAuthenticationConfig the REST authentication configuration + */ public AuthorizingInterceptor(RestAuthenticationConfig restAuthenticationConfig) { super(); setGlobalRoles(restAuthenticationConfig.getGlobalRoles()); diff --git a/rest/src/main/java/org/apache/unomi/rest/authentication/SecurityContextCleanupFilter.java b/rest/src/main/java/org/apache/unomi/rest/authentication/SecurityContextCleanupFilter.java index cf159aea3..85d3ec079 100644 --- a/rest/src/main/java/org/apache/unomi/rest/authentication/SecurityContextCleanupFilter.java +++ b/rest/src/main/java/org/apache/unomi/rest/authentication/SecurityContextCleanupFilter.java @@ -38,6 +38,12 @@ public class SecurityContextCleanupFilter implements ContainerResponseFilter { private final SecurityService securityService; private final ExecutionContextManager executionContextManager; + /** + * Creates the cleanup filter. + * + * @param securityService the security service + * @param executionContextManager the execution context manager + */ public SecurityContextCleanupFilter(SecurityService securityService, ExecutionContextManager executionContextManager) { this.securityService = securityService; this.executionContextManager = executionContextManager; diff --git a/rest/src/main/java/org/apache/unomi/rest/authentication/V2ThirdPartyConfigService.java b/rest/src/main/java/org/apache/unomi/rest/authentication/V2ThirdPartyConfigService.java index 836cf8ef2..56dff48df 100644 --- a/rest/src/main/java/org/apache/unomi/rest/authentication/V2ThirdPartyConfigService.java +++ b/rest/src/main/java/org/apache/unomi/rest/authentication/V2ThirdPartyConfigService.java @@ -37,17 +37,18 @@ import java.util.Set; /** - * Service to handle V2 third-party configuration for V2 compatibility mode. - * This service reads the legacy V2 third-party configuration and provides - * methods to validate protected events and third-party providers. - * Uses the original V2 configuration file: org.apache.unomi.thirdparty.cfg + * OSGi service that loads V2 third-party provider configuration from {@code org.apache.unomi.thirdparty.cfg} + * and validates protected events and provider keys in V2 compatibility mode. */ @Component(service = V2ThirdPartyConfigService.class, configurationPid = "org.apache.unomi.thirdparty") @Designate(ocd = V2ThirdPartyConfigService.Config.class) public class V2ThirdPartyConfigService { - + private static final Logger LOGGER = LoggerFactory.getLogger(V2ThirdPartyConfigService.class); - + + /** + * OSGi configuration for V2 third-party providers. + */ @ObjectClassDefinition( name = "Apache Unomi Third-Party Configuration", description = "Configuration for third-party providers (V2 compatibility mode). " + @@ -58,33 +59,43 @@ public class V2ThirdPartyConfigService { // No hardcoded attributes - all providers are configured dynamically // using the pattern: thirdparty.{providerName}.{property} } - + /** - * Provider configuration data structure + * Provider configuration entry parsed from OSGi properties. */ private static class ProviderConfig { private final String key; private final Set ipAddresses; private final Set allowedEvents; - + public ProviderConfig(String key, Set ipAddresses, Set allowedEvents) { this.key = key; this.ipAddresses = ipAddresses; this.allowedEvents = allowedEvents; } - + public String getKey() { return key; } public Set getIpAddresses() { return ipAddresses; } public Set getAllowedEvents() { return allowedEvents; } } - + private volatile Map providers = new HashMap<>(); - + + /** + * Activates the service and loads third-party provider configuration. + * + * @param properties the OSGi configuration properties + */ @Activate public void activate(Map properties) { modified(properties); } - + + /** + * Reloads third-party provider configuration. + * + * @param properties the OSGi configuration properties + */ @Modified public void modified(Map properties) { Map newProviders = new HashMap<>(); @@ -119,7 +130,7 @@ public void modified(Map properties) { } } } - + if (newProviders.isEmpty()) { // The fallback key below is the well-known Unomi V2 default key, publicly documented // in Apache Unomi changelogs and issue trackers. It provides no confidentiality on its own @@ -134,36 +145,36 @@ public void modified(Map properties) { new HashSet<>(Arrays.asList("login", "updateProperties")) )); } - + this.providers = newProviders; - + int totalEvents = newProviders.values().stream() .mapToInt(config -> config.getAllowedEvents().size()) .sum(); - - LOGGER.info("V2 Third-Party Configuration updated - {} providers with {} total protected events", + + LOGGER.info("V2 Third-Party Configuration updated - {} providers with {} total protected events", newProviders.size(), totalEvents); } - + /** - * Check if an event type is protected (requires third-party authentication). - * + * Returns whether the event type requires third-party authentication. + * * @param eventType the event type to check - * @return true if the event type is protected, false otherwise + * @return {@code true} when the event type is protected */ public boolean isProtectedEventType(String eventType) { if (StringUtils.isBlank(eventType)) { return false; } - + return providers.values().stream() .anyMatch(config -> config.getAllowedEvents().contains(eventType)); } - + /** - * Get all protected event types from all providers. - * - * @return set of all protected event types + * Returns all protected event types declared by configured providers. + * + * @return an unmodifiable set of protected event type names */ public Set getAllProtectedEventTypes() { Set allProtectedEvents = new HashSet<>(); @@ -172,22 +183,20 @@ public Set getAllProtectedEventTypes() { } return Collections.unmodifiableSet(allProtectedEvents); } - /** - * Validate a third-party provider by key for a given event type. - * This method is used when the X-Unomi-Peer header contains the provider key. - * - * @param providerKey the third-party provider key (from X-Unomi-Peer header) + * Validates a provider key from the {@code X-Unomi-Peer} header for an event and source IP. + * + * @param providerKey the third-party provider key from the request header * @param eventType the event type to validate * @param sourceIP the source IP address - * @return true if the provider is authorized for this event type and IP, false otherwise + * @return {@code true} when the provider is authorized for the event and IP */ public boolean validateProviderByKey(String providerKey, String eventType, String sourceIP) { if (StringUtils.isBlank(providerKey) || StringUtils.isBlank(eventType) || StringUtils.isBlank(sourceIP)) { return false; } - + // Find the provider that has the matching key ProviderConfig config = null; String foundProviderId = null; @@ -198,7 +207,7 @@ public boolean validateProviderByKey(String providerKey, String eventType, Strin break; } } - + if (config == null) { LOGGER.debug("V2 compatibility mode: Unknown provider key: {}", SecurityUtils.maskSecret(providerKey)); return false; @@ -213,36 +222,36 @@ public boolean validateProviderByKey(String providerKey, String eventType, Strin if (!ipAuthorized) { LOGGER.debug("V2 compatibility mode: IP {} not authorized for provider {} (key: {})", sourceIP, foundProviderId, SecurityUtils.maskSecret(providerKey)); } - + return ipAuthorized; } - + /** - * Get the key for a third-party provider. - * + * Returns the authentication key for the given provider ID. + * * @param providerId the third-party provider ID - * @return the provider key, or null if not found + * @return the provider key, or {@code null} when the provider is unknown */ public String getProviderKey(String providerId) { ProviderConfig config = providers.get(providerId); return config != null ? config.getKey() : null; } - + /** - * Check if a provider ID is valid. - * + * Returns whether the provider ID is configured. + * * @param providerId the third-party provider ID - * @return true if the provider ID is valid, false otherwise + * @return {@code true} when the provider ID is known */ public boolean isValidProvider(String providerId) { return providers.containsKey(providerId); } - + private Set parseCommaSeparatedList(String value) { if (StringUtils.isBlank(value)) { return new HashSet<>(); } - + Set result = new HashSet<>(); String[] parts = value.split(","); for (String part : parts) { @@ -253,6 +262,6 @@ private Set parseCommaSeparatedList(String value) { } return result; } - + } diff --git a/rest/src/main/java/org/apache/unomi/rest/authentication/impl/DefaultRestAuthenticationConfig.java b/rest/src/main/java/org/apache/unomi/rest/authentication/impl/DefaultRestAuthenticationConfig.java index 1e2cb9e89..2d3bf5fa1 100644 --- a/rest/src/main/java/org/apache/unomi/rest/authentication/impl/DefaultRestAuthenticationConfig.java +++ b/rest/src/main/java/org/apache/unomi/rest/authentication/impl/DefaultRestAuthenticationConfig.java @@ -32,7 +32,8 @@ import java.util.regex.Pattern; /** - * Default implementation for the unomi authentication on Rest endpoints + * Default OSGi-backed implementation of {@link RestAuthenticationConfig} for REST endpoint + * authentication, role mappings, and V2 compatibility mode settings. */ @Component(service = { RestAuthenticationConfig.class}, configurationPid = "org.apache.unomi.rest.authentication", immediate = true) @Designate(ocd = DefaultRestAuthenticationConfig.Config.class) @@ -78,7 +79,11 @@ public class DefaultRestAuthenticationConfig implements RestAuthenticationConfig private volatile boolean v2CompatibilityModeEnabled = false; private volatile String v2CompatibilityDefaultTenantId = "default"; - + /** + * Updates authentication settings from OSGi configuration. + * + * @param config the REST authentication configuration + */ @Activate @Modified public void modified(Config config) { @@ -127,18 +132,31 @@ public String getV2CompatibilityDefaultTenantId() { return v2CompatibilityDefaultTenantId; } + /** + * OSGi configuration for REST authentication. + */ @ObjectClassDefinition( name = "Unomi REST Authentication Configuration", description = "Configuration for Unomi REST authentication including V2 compatibility mode" ) public @interface Config { + /** + * Whether V2 compatibility mode is enabled. + * + * @return {@code true} when V2 compatibility mode is enabled + */ @AttributeDefinition( name = "V2 Compatibility Mode Enabled", description = "Enable V2 compatibility mode to allow V2 clients to use Unomi V3 without API keys" ) boolean v2_compatibilitymode_enabled() default false; + /** + * Default tenant identifier used in V2 compatibility mode. + * + * @return the default tenant identifier + */ @AttributeDefinition( name = "V2 Compatibility Default Tenant ID", description = "Default tenant ID to use in V2 compatibility mode" diff --git a/rest/src/main/java/org/apache/unomi/rest/deserializers/ContextRequestDeserializer.java b/rest/src/main/java/org/apache/unomi/rest/deserializers/ContextRequestDeserializer.java index 92e75277a..1e4b28726 100644 --- a/rest/src/main/java/org/apache/unomi/rest/deserializers/ContextRequestDeserializer.java +++ b/rest/src/main/java/org/apache/unomi/rest/deserializers/ContextRequestDeserializer.java @@ -42,10 +42,21 @@ public class ContextRequestDeserializer extends StdDeserializer private final SchemaService schemaService; + /** + * Creates a deserializer for ContextRequest. + * + * @param schemaRegistry the schema service + */ public ContextRequestDeserializer(SchemaService schemaRegistry) { this(null, schemaRegistry); } + /** + * Creates a deserializer for ContextRequest. + * + * @param vc the target class + * @param schemaRegistry the schema service + */ public ContextRequestDeserializer(Class vc, SchemaService schemaRegistry) { super(vc); this.schemaService = schemaRegistry; diff --git a/rest/src/main/java/org/apache/unomi/rest/deserializers/EventsCollectorRequestDeserializer.java b/rest/src/main/java/org/apache/unomi/rest/deserializers/EventsCollectorRequestDeserializer.java index 022cb9624..459930d56 100644 --- a/rest/src/main/java/org/apache/unomi/rest/deserializers/EventsCollectorRequestDeserializer.java +++ b/rest/src/main/java/org/apache/unomi/rest/deserializers/EventsCollectorRequestDeserializer.java @@ -39,10 +39,21 @@ public class EventsCollectorRequestDeserializer extends StdDeserializer vc, SchemaService schemaService) { super(vc); this.schemaService = schemaService; diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/CampaignsServiceEndPoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/CampaignsServiceEndPoint.java index 0e23f67da..b40061e20 100644 --- a/rest/src/main/java/org/apache/unomi/rest/endpoints/CampaignsServiceEndPoint.java +++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/CampaignsServiceEndPoint.java @@ -53,18 +53,26 @@ public class CampaignsServiceEndPoint { @Reference private GoalsService goalsService; + /** + * Creates the campaigns service endpoint. + */ public CampaignsServiceEndPoint() { LOGGER.info("Initializing campaigns service endpoint..."); } + /** + * Sets the goals service. + * + * @param goalsService the goals service + */ public void setGoalsService(GoalsService goalsService) { this.goalsService = goalsService; } /** - * Retrieves the set of Metadata associated with existing campaigns. + * Returns metadata for all campaigns. * - * @return the set of Metadata associated with existing campaigns + * @return campaign metadata for every stored campaign */ @GET @Path("/") @@ -84,10 +92,10 @@ public void setCampaignDefinition(Campaign campaign) { } /** - * Retrieves the set of Metadata associated with existing campaign matching the specified {@link Query} + * Returns campaign metadata matching the given query. * - * @param query the Query used to filter the campagins which metadata we want to retrieve - * @return the set of Metadata associated with existing campaigns matching the specified {@link Query} + * @param query the query used to filter campaigns + * @return metadata for campaigns that match the query */ @POST @Path("/query") @@ -96,10 +104,10 @@ public Set getCampaignMetadatas(Query query) { } /** - * Retrieves campaign details for campaigns matching the specified query. + * Returns detailed campaign data matching the given query. * - * @param query the query specifying which campaigns to retrieve - * @return a {@link PartialList} of campaign details for the campaigns matching the specified query + * @param query the query specifying which campaigns to include + * @return a paged list of campaign details */ @POST @Path("/query/detailed") @@ -108,10 +116,10 @@ public PartialList getCampaignDetails(Query query) { } /** - * Retrieves the {@link CampaignDetail} associated with the campaign identified with the specified identifier + * Returns detailed data for the campaign with the given ID. * - * @param campaignID the identifier of the campaign for which we want to retrieve the details - * @return the CampaignDetail for the campaign identified by the specified identifier or {@code null} if no such campaign exists + * @param campaignID the campaign identifier + * @return the campaign detail, or {@code null} when no such campaign exists */ @GET @Path("/{campaignID}/detailed") @@ -120,10 +128,10 @@ public CampaignDetail getCampaignDetail(@PathParam("campaignID") String campaign } /** - * Retrieves the campaign identified by the specified identifier + * Returns the campaign definition for the given ID. * - * @param campaignID the identifier of the campaign we want to retrieve - * @return the campaign associated with the specified identifier or {@code null} if no such campaign exists + * @param campaignID the campaign identifier + * @return the campaign, or {@code null} when no such campaign exists */ @GET @Path("/{campaignID}") @@ -165,10 +173,10 @@ public void removeCampaignEventDefinition(@PathParam("eventId") String campaignE } /** - * Retrieves {@link CampaignEvent}s matching the specified query. + * Returns campaign events matching the given query. * - * @param query the Query specifying which CampaignEvents to retrieve - * @return a {@link PartialList} of campaign events matching the specified query + * @param query the query specifying which campaign events to include + * @return a paged list of matching campaign events */ @POST @Path("/events/query") diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/ClientEndpoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/ClientEndpoint.java index acc087cf9..2197b5c72 100644 --- a/rest/src/main/java/org/apache/unomi/rest/endpoints/ClientEndpoint.java +++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/ClientEndpoint.java @@ -69,12 +69,25 @@ private static String getContentDispostionHeader(String extension) { @Context HttpServletResponse response; + /** + * Handles CORS preflight for the client endpoint. + * + * @return an empty CORS preflight response + */ @OPTIONS @Path("/client/{operation}/{param}") public Response options() { return Response.status(Response.Status.NO_CONTENT).build(); } + /** + * Exports profile data for the requested client operation. + * + * @param operation the export operation + * @param param the operation parameter + * @return the export response + * @throws JsonProcessingException if response serialization fails + */ @GET @Path("/client/{operation}/{param}") public Response getClient(@PathParam("operation") String operation, @PathParam("param") String param) throws JsonProcessingException { diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/ClusterServiceEndPoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/ClusterServiceEndPoint.java index 19fff95a5..23fdc3232 100644 --- a/rest/src/main/java/org/apache/unomi/rest/endpoints/ClusterServiceEndPoint.java +++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/ClusterServiceEndPoint.java @@ -34,7 +34,7 @@ import java.util.List; /** - * A JAX-RS endpoint to access information about the context server's cluster. + * JAX-RS endpoint for cluster node visibility and data purge operations. */ @Produces(MediaType.APPLICATION_JSON) @CrossOriginResourceSharing( @@ -52,22 +52,35 @@ public class ClusterServiceEndPoint { @Reference private ClusterService clusterService; + /** + * Creates the cluster service endpoint. + */ public ClusterServiceEndPoint() { LOGGER.info("Initializing cluster service endpoint..."); } + /** + * Sets the cluster service. + * + * @param clusterService the cluster service + */ public void setClusterService(ClusterService clusterService) { this.clusterService = clusterService; } + /** + * Sets the CXF message context. + * + * @param messageContext the message context + */ public void setMessageContext(MessageContext messageContext) { this.messageContext = messageContext; } /** - * Retrieves the list of available nodes for this context server instance. + * Returns cluster nodes known to this server instance. * - * @return a list of {@link ClusterNode} + * @return the available cluster nodes */ @GET @Path("/") diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java index bcec27d77..7543b8e35 100644 --- a/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java +++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/ContextJsonEndpoint.java @@ -51,6 +51,9 @@ import java.util.*; import java.util.stream.Collectors; +/** + * REST endpoint for context.js and context.json requests. + */ @Consumes(MediaType.APPLICATION_JSON) @CrossOriginResourceSharing(allowAllOrigins = true, allowCredentials = true) @Path("/") @@ -83,18 +86,42 @@ public class ContextJsonEndpoint { @Reference private TracerService tracerService; + /** + * Handles CORS preflight for context.js. + * + * @return an empty CORS preflight response + */ @OPTIONS @Path("/context.js") public Response contextJSAsOptions() { return Response.status(Response.Status.NO_CONTENT).header("Access-Control-Allow-Origin", "*").build(); } + /** + * Handles CORS preflight for context.json. + * + * @return an empty CORS preflight response + */ @OPTIONS @Path("/context.json") public Response contextJSONAsOptions() { return contextJSAsOptions(); } + /** + * Processes a context.js POST request. + * + * @param contextRequest the context request payload + * @param personaId optional persona identifier + * @param sessionId optional session identifier + * @param timestampAsLong optional request timestamp + * @param invalidateProfile whether to invalidate the profile + * @param invalidateSession whether to invalidate the session + * @param explain whether to include tracing details + * @param securityContext the security context + * @return the context response wrapped as JavaScript + * @throws JsonProcessingException if response serialization fails + */ @POST @Produces(MediaType.TEXT_PLAIN) @Path("/context.js") @@ -109,6 +136,20 @@ public Response contextJSAsPost(ContextRequest contextRequest, return contextJSAsGet(contextRequest, personaId, sessionId, timestampAsLong, invalidateProfile, invalidateSession, explain, securityContext); } + /** + * Processes a context.js GET request. + * + * @param contextRequest the context request payload + * @param personaId optional persona identifier + * @param sessionId optional session identifier + * @param timestampAsLong optional request timestamp + * @param invalidateProfile whether to invalidate the profile + * @param invalidateSession whether to invalidate the session + * @param explain whether to include tracing details + * @param securityContext the security context + * @return the context response wrapped as JavaScript + * @throws JsonProcessingException if response serialization fails + */ @GET @Produces(MediaType.TEXT_PLAIN) @Path("/context.js") @@ -129,6 +170,19 @@ public Response contextJSAsGet(@QueryParam("payload") ContextRequest contextRequ return Response.ok(responseAsString.toString()).build(); } + /** + * Processes a context.json GET request. + * + * @param contextRequest the context request payload + * @param personaId optional persona identifier + * @param sessionId optional session identifier + * @param timestampAsLong optional request timestamp + * @param invalidateProfile whether to invalidate the profile + * @param invalidateSession whether to invalidate the session + * @param explain whether to include tracing details + * @param securityContext the security context + * @return the context response + */ @GET @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8") @Path("/context.json") @@ -143,6 +197,19 @@ public ContextResponse contextJSONAsGet(@QueryParam("payload") ContextRequest co return contextJSONAsPost(contextRequest, personaId, sessionId, timestampAsLong, invalidateProfile, invalidateSession, explain, securityContext); } + /** + * Processes a context.json POST request. + * + * @param contextRequest the context request payload + * @param personaId optional persona identifier + * @param sessionId optional session identifier + * @param timestampAsLong optional request timestamp + * @param invalidateProfile whether to invalidate the profile + * @param invalidateSession whether to invalidate the session + * @param explain whether to include tracing details + * @param securityContext the security context + * @return the context response + */ @POST @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8") @Path("/context.json") @@ -336,6 +403,9 @@ private void processOverrides(ContextRequest contextRequest, Profile profile, Se } } + /** + * Shuts down the endpoint. + */ public void destroy() { LOGGER.info("Context servlet shutdown."); } diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/DefinitionsServiceEndPoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/DefinitionsServiceEndPoint.java index a44655127..97b9a1697 100644 --- a/rest/src/main/java/org/apache/unomi/rest/endpoints/DefinitionsServiceEndPoint.java +++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/DefinitionsServiceEndPoint.java @@ -36,7 +36,7 @@ import java.util.*; /** - * A JAX-RS endpoint to retrieve definition information about core context server entities such as conditions, actions and values. + * JAX-RS endpoint for condition, action, and value type definitions used by the rules engine. */ @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8") @CrossOriginResourceSharing( @@ -52,19 +52,29 @@ public class DefinitionsServiceEndPoint { @Reference private LocalizationHelper localizationHelper; + /** + * Sets the definitions service. + * + * @param definitionsService the definitions service + */ public void setDefinitionsService(DefinitionsService definitionsService) { this.definitionsService = definitionsService; } + /** + * Sets the localization helper. + * + * @param localizationHelper the localization helper + */ public void setLocalizationHelper(LocalizationHelper localizationHelper) { this.localizationHelper = localizationHelper; } /** - * Retrieves all condition types localized using the specified language. + * Returns all condition types localized for the requested language. * - * @param language the language to use to localize. - * @return a Collection of all collection types + * @param language the locale to use for labels and descriptions + * @return all condition types in REST form */ @GET @Path("/conditions") @@ -74,11 +84,11 @@ public Collection getAllConditionTypes(@HeaderParam("Accept-L } /** - * Retrieves the set of condition types with the specified tags. + * Returns condition types that match any of the given tags. * - * @param language the language to use to localize. - * @param tags a comma-separated list of tag identifiers - * @return the set of condition types with the specified tag + * @param language the locale to use for labels and descriptions + * @param tags a comma-separated list of tag identifiers + * @return matching condition types in REST form */ @GET @Path("/conditions/tags/{tags}") @@ -92,11 +102,11 @@ public Collection getConditionTypesByTag(@PathParam("tags") S } /** - * Retrieves the set of condition types with the specified system tags. + * Returns condition types that match any of the given system tags. * - * @param language the language to use to localize. - * @param tags a comma-separated list of tag identifiers - * @return the set of condition types with the specified tag + * @param language the locale to use for labels and descriptions + * @param tags a comma-separated list of system tag identifiers + * @return matching condition types in REST form */ @GET @Path("/conditions/systemTags/{tags}") @@ -110,11 +120,11 @@ public Collection getConditionTypesBySystemTag(@PathParam("ta } /** - * Retrieves the condition type associated with the specified identifier localized using the specified language. + * Returns the condition type with the given ID, localized for the requested language. * - * @param language the language to use to localize. - * @param id the identifier of the condition type to retrieve - * @return the condition type associated with the specified identifier or {@code null} if no such condition type exists + * @param language the locale to use for labels and descriptions + * @param id the condition type identifier + * @return the condition type in REST form, or {@code null} when it does not exist */ @GET @Path("/conditions/{conditionId}") @@ -124,7 +134,7 @@ public RESTConditionType getConditionType(@PathParam("conditionId") String id, @ } /** - * Stores the condition type + * Stores the given condition type definition. * * @param conditionType the condition type to store */ @@ -135,9 +145,9 @@ public void setConditionType(ConditionType conditionType) { } /** - * Removes the condition type + * Deletes the condition type with the given ID. * - * @param conditionTypeId the identifier of the action type to delete + * @param conditionTypeId the condition type identifier */ @DELETE @Path("/conditions/{conditionTypeId}") @@ -146,10 +156,10 @@ public void removeConditionType(@PathParam("conditionTypeId") String conditionTy } /** - * Retrieves all known action types localized using the specified language. + * Returns all action types localized for the requested language. * - * @param language the language to use to localize. - * @return all known action types + * @param language the locale to use for labels and descriptions + * @return all action types in REST form */ @GET @Path("/actions") @@ -159,11 +169,11 @@ public Collection getAllActionTypes(@HeaderParam("Accept-Languag } /** - * Retrieves the set of action types with the specified tags. + * Returns action types that match any of the given tags. * - * @param language the language to use to localize. - * @param tags the tag marking the action types we want to retrieve - * @return the set of action types with the specified tag + * @param language the locale to use for labels and descriptions + * @param tags a comma-separated list of tag identifiers + * @return matching action types in REST form */ @GET @Path("/actions/tags/{tags}") @@ -177,11 +187,11 @@ public Collection getActionTypeByTag(@PathParam("tags") String t } /** - * Retrieves the set of action types with the specified system tags. + * Returns action types that match any of the given system tags. * - * @param language the language to use to localize. - * @param tags the tag marking the action types we want to retrieve - * @return the set of action types with the specified tag + * @param language the locale to use for labels and descriptions + * @param tags a comma-separated list of system tag identifiers + * @return matching action types in REST form */ @GET @Path("/actions/systemTags/{tags}") @@ -195,11 +205,11 @@ public Collection getActionTypeBySystemTag(@PathParam("tags") St } /** - * Retrieves the action type associated with the specified identifier localized using the specified language. + * Returns the action type with the given ID, localized for the requested language. * - * @param language the language to use to localize. - * @param id the identifier of the action type to retrieve - * @return the action type associated with the specified identifier or {@code null} if no such action type exists + * @param language the locale to use for labels and descriptions + * @param id the action type identifier + * @return the action type in REST form, or {@code null} when it does not exist */ @GET @Path("/actions/{actionId}") @@ -209,7 +219,7 @@ public RESTActionType getActionType(@PathParam("actionId") String id, @HeaderPar } /** - * Stores the action type + * Stores the given action type definition. * * @param actionType the action type to store */ @@ -220,9 +230,9 @@ public void setActionType(ActionType actionType) { } /** - * Removes the action type + * Deletes the action type with the given ID. * - * @param actionTypeId the identifier of the action type to delete + * @param actionTypeId the action type identifier */ @DELETE @Path("/actions/{actionTypeId}") @@ -231,10 +241,10 @@ public void removeActionType(@PathParam("actionTypeId") String actionTypeId) { } /** - * Retrieves all known value types localized using the specified language. + * Returns all value types localized for the requested language. * - * @param language the language to use to localize. - * @return all known value types + * @param language the locale to use for labels and descriptions + * @return all value types in REST form */ @GET @Path("/values") @@ -243,11 +253,11 @@ public Collection getAllValueTypes(@HeaderParam("Accept-Language" } /** - * Retrieves the set of value types with the specified tags. + * Returns value types that match any of the given tags. * - * @param language the language to use to localize. - * @param tags the tag marking the value types we want to retrieve - * @return the set of value types with the specified tag + * @param language the locale to use for labels and descriptions + * @param tags a comma-separated list of tag identifiers + * @return matching value types in REST form */ @GET @Path("/values/tags/{tags}") @@ -261,11 +271,11 @@ public Collection getValueTypeByTag(@PathParam("tags") String tag } /** - * Retrieves the value type associated with the specified identifier localized using the specified language. + * Returns the value type with the given ID, localized for the requested language. * - * @param language the language to use to localize. - * @param id the identifier of the value type to retrieve - * @return the value type associated with the specified identifier or {@code null} if no such value type exists + * @param language the locale to use for labels and descriptions + * @param id the value type identifier + * @return the value type in REST form, or {@code null} when it does not exist */ @GET @Path("/values/{valueTypeId}") @@ -275,9 +285,9 @@ public RESTValueType getValueType(@PathParam("valueTypeId") String id, @HeaderPa } /** - * Retrieves a Map of plugin identifier to a list of plugin types defined by that particular plugin. + * Returns plugin types grouped by plugin identifier. * - * @return a Map of plugin identifier to a list of plugin types defined by that particular plugin + * @return plugin ID to plugin type list mappings */ @GET @Path("/typesByPlugin") @@ -285,6 +295,12 @@ public Map> getTypesByPlugin() { return definitionsService.getTypesByPlugin(); } + /** + * Returns the property merge strategy type for the given identifier. + * + * @param id the property merge strategy type identifier + * @return the property merge strategy type + */ public PropertyMergeStrategyType getPropertyMergeStrategyType(String id) { return definitionsService.getPropertyMergeStrategyType(id); } diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/EventServiceEndpoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/EventServiceEndpoint.java index 0dc3df331..c36ff131a 100644 --- a/rest/src/main/java/org/apache/unomi/rest/endpoints/EventServiceEndpoint.java +++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/EventServiceEndpoint.java @@ -29,7 +29,7 @@ import java.util.Set; /** - * A JAX-RS endpoint to access information about the context server's events. + * JAX-RS endpoint for searching, loading, and deleting stored events. */ @Produces(MediaType.APPLICATION_JSON) @CrossOriginResourceSharing( @@ -43,16 +43,20 @@ public class EventServiceEndpoint { @Reference private EventService eventService; + /** + * Sets the event service. + * + * @param eventService the event service + */ public void setEventService(EventService eventService) { this.eventService = eventService; } /** - * Allows to search events using a query. + * Searches events using the given query. * - * @param query the query object to use to search for events. You can specify offset and limits along with a - * condition tree. - * @return a partial list containing the events that match the query. + * @param query the search query, including optional condition tree, offset, and limit + * @return a paged list of matching events */ @POST @Path("/search") @@ -61,10 +65,10 @@ public PartialList searchEvents(Query query) { } /** - * Allows to retrieve event by id. + * Returns the event with the given ID. * - * @param id the event id. - * @return {@link Event} with the provided id. + * @param id the event identifier + * @return the event, or {@code null} when it does not exist */ @GET @Path("/{id}") @@ -84,8 +88,9 @@ public void deleteEvent(@PathParam("id") final String id) { } /** - * Retrieves the list of event types identifiers that the server has processed. - * @return a Set of strings that contain event type identifiers. + * Returns event type identifiers known to the server. + * + * @return the processed event type identifiers */ @GET @Path("types") diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/EventsCollectorEndpoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/EventsCollectorEndpoint.java index 6945b03b6..1cfa8a1fe 100644 --- a/rest/src/main/java/org/apache/unomi/rest/endpoints/EventsCollectorEndpoint.java +++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/EventsCollectorEndpoint.java @@ -42,6 +42,9 @@ import java.util.Date; import java.util.List; +/** + * REST endpoint for collecting client events. + */ @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8") @Consumes(MediaType.APPLICATION_JSON) @CrossOriginResourceSharing(allowAllOrigins = true, allowCredentials = true) @@ -63,12 +66,26 @@ public class EventsCollectorEndpoint { @Context HttpServletResponse response; + /** + * Handles CORS preflight for the event collector endpoint. + * + * @return an empty CORS preflight response + */ @OPTIONS @Path("/eventcollector") public Response options() { return Response.status(Response.Status.NO_CONTENT).build(); } + /** + * Collects events from a GET request. + * + * @param eventsCollectorRequest the events collector request + * @param timestampAsString optional request timestamp + * @param explain whether to include tracing details + * @param securityContext the security context + * @return the event collector response + */ @GET @Path("/eventcollector") public EventCollectorResponse collectAsGet(@QueryParam("payload") EventsCollectorRequest eventsCollectorRequest, @@ -78,6 +95,15 @@ public EventCollectorResponse collectAsGet(@QueryParam("payload") EventsCollecto return doEvent(eventsCollectorRequest, timestampAsString, explain, securityContext); } + /** + * Collects events from a POST request. + * + * @param eventsCollectorRequest the events collector request + * @param timestampAsLong optional request timestamp + * @param explain whether to include tracing details + * @param securityContext the security context + * @return the event collector response + */ @POST @Path("/eventcollector") public EventCollectorResponse collectAsPost(EventsCollectorRequest eventsCollectorRequest, diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/GoalsServiceEndPoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/GoalsServiceEndPoint.java index 68121e62e..3c750fd69 100644 --- a/rest/src/main/java/org/apache/unomi/rest/endpoints/GoalsServiceEndPoint.java +++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/GoalsServiceEndPoint.java @@ -48,14 +48,19 @@ public class GoalsServiceEndPoint { @Reference private GoalsService goalsService; + /** + * Sets the goals service. + * + * @param goalsService the goals service + */ public void setGoalsService(GoalsService goalsService) { this.goalsService = goalsService; } /** - * Retrieves the set of Metadata associated with existing goals. + * Returns metadata for all goals. * - * @return the set of Metadata associated with existing goals + * @return goal metadata for every stored goal */ @GET @Path("/") @@ -63,7 +68,6 @@ public Set getGoalMetadatas() { return goalsService.getGoalMetadatas(); } - /** * Saves the specified goal in the context server and creates associated {@link Rule}s if the goal is enabled. * @@ -76,10 +80,10 @@ public void setGoal(Goal goal) { } /** - * Retrieves the set of Metadata associated with existing goals matching the specified {@link Query} + * Returns goal metadata matching the given query. * - * @param query the Query used to filter the Goals which metadata we want to retrieve - * @return the set of Metadata associated with existing goals matching the specified {@link Query} + * @param query the query used to filter goals + * @return metadata for goals that match the query */ @POST @Path("/query") @@ -88,10 +92,10 @@ public Set getGoalMetadatas(Query query) { } /** - * Retrieves the goal associated with the specified identifier. + * Returns the goal with the given ID. * - * @param goalId the identifier of the goal to retrieve - * @return the goal associated with the specified identifier or {@code null} if no such goal exists + * @param goalId the goal identifier + * @return the goal, or {@code null} when it does not exist */ @GET @Path("/{goalId}") @@ -111,10 +115,10 @@ public void removeGoal(@PathParam("goalId") String goalId) { } /** - * Retrieves the report for the goal identified with the specified identifier. + * Returns the performance report for the goal with the given ID. * - * @param goalId the identifier of the goal which report we want to retrieve - * @return the report for the specified goal + * @param goalId the goal identifier + * @return the goal report */ @GET @Path("/{goalID}/report") @@ -123,11 +127,11 @@ public GoalReport getGoalReport(@PathParam("goalID") String goalId) { } /** - * Retrieves the report for the goal identified with the specified identifier, considering only elements determined by the specified {@link AggregateQuery}. + * Returns a filtered goal report for the given goal and aggregate query. * - * @param goalId the identifier of the goal which report we want to retrieve - * @param query an {@link AggregateQuery} to further specify which elements of the report we want - * @return the report for the specified goal and query + * @param goalId the goal identifier + * @param query the aggregate query that limits report elements + * @return the filtered goal report */ @POST @Path("/{goalID}/report") diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/PatchServiceEndPoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/PatchServiceEndPoint.java index 3f6d81f68..19fe51fb4 100644 --- a/rest/src/main/java/org/apache/unomi/rest/endpoints/PatchServiceEndPoint.java +++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/PatchServiceEndPoint.java @@ -42,12 +42,18 @@ public class PatchServiceEndPoint { @Reference private PatchService patchService; + /** + * Sets the patch service. + * + * @param patchService the patch service + */ public void setPatchService(PatchService patchService) { this.patchService = patchService; } /** * Apply a patch on an item + * * @param patch the patch to apply * @param force a boolean to force (or not) the application of the patch even if it was previously applied. */ diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/ProfileServiceEndPoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/ProfileServiceEndPoint.java index 26fe38b6c..b03208e87 100644 --- a/rest/src/main/java/org/apache/unomi/rest/endpoints/ProfileServiceEndPoint.java +++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/ProfileServiceEndPoint.java @@ -69,30 +69,53 @@ public class ProfileServiceEndPoint { @Reference private LocalizationHelper localizationHelper; + /** + * Creates the profile service endpoint. + */ public ProfileServiceEndPoint() { LOGGER.info("Initializing profile service endpoint..."); } + /** + * Sets the profile service. + * + * @param profileService the profile service + */ public void setProfileService(ProfileService profileService) { this.profileService = profileService; } + /** + * Sets the event service. + * + * @param eventService the event service + */ public void setEventService(EventService eventService) { this.eventService = eventService; } + /** + * Sets the segment service. + * + * @param segmentService the segment service + */ public void setSegmentService(SegmentService segmentService) { this.segmentService = segmentService; } + /** + * Sets the localization helper. + * + * @param localizationHelper the localization helper + */ public void setLocalizationHelper(LocalizationHelper localizationHelper) { this.localizationHelper = localizationHelper; } /** - * Retrieves the number of unique profiles. + * Returns the total number of unique profiles. * - * @return the number of unique profiles. + * @return the profile count */ @GET @Path("/count") @@ -101,10 +124,10 @@ public long getAllProfilesCount() { } /** - * Retrieves profiles matching the specified query. + * Returns profiles matching the given query. * - * @param query a {@link Query} specifying which elements to retrieve - * @return a {@link PartialList} of profiles instances matching the specified query + * @param query the search query + * @return a paged list of matching profiles */ @POST @Path("/search") @@ -113,10 +136,10 @@ public PartialList getProfiles(Query query) { } /** - * Retrieves an export of profiles matching the specified query as a downloadable file using the comma-separated values (CSV) format. + * Exports matching profiles as a downloadable CSV file. * - * @param query a String JSON representation of the query the profiles to export should match - * @return a Response object configured to allow caller to download the CSV export file + * @param query JSON query string describing which profiles to export + * @return a CSV download response */ @GET @Path("/export") @@ -150,10 +173,10 @@ public Response formExportProfiles(@FormParam("query") String query) { } /** - * Retrieves an export of profiles matching the specified query as a downloadable file using the comma-separated values (CSV) format. + * Exports matching profiles as a downloadable CSV file. * - * @param query a String JSON representation of the query the profiles to export should match - * @return a Response object configured to allow caller to download the CSV export file + * @param query the query describing which profiles to export + * @return a CSV download response */ @POST @Path("/export") @@ -167,7 +190,7 @@ public Response exportProfiles(Query query) { } /** - * Update all profiles in batch according to the specified {@link BatchUpdate} + * Applies a batch update to all matching profiles. * * @param update the batch update specification */ @@ -178,10 +201,10 @@ public void batchProfilesUpdate(BatchUpdate update) { } /** - * Retrieves the profile identified by the specified identifier. + * Returns the profile with the given ID. * - * @param profileId the identifier of the profile to retrieve - * @return the profile identified by the specified identifier or {@code null} if no such profile exists + * @param profileId the profile identifier + * @return the profile, or {@code null} when it does not exist */ @GET @Path("/{profileId}") @@ -223,20 +246,16 @@ public void delete(@PathParam("profileId") String profileId, @QueryParam("person } /** - * Retrieves the sessions associated with the profile identified by the specified identifier that match the specified query (if specified), ordered according to the specified - * {@code sortBy} String and and paged: only {@code size} of them are retrieved, starting with the {@code offset}-th one. + * Returns sessions for the profile with the given ID. + *

+ * Results can be filtered with an optional full-text query and paged with offset, size, and sort parameters. * - * TODO: use a Query object instead of distinct parameter? - * - * @param profileId the identifier of the profile we want to retrieve sessions from - * @param query a String of text used for fulltext filtering which sessions we are interested in or {@code null} (or an empty String) if we want to retrieve all sessions - * @param offset zero or a positive integer specifying the position of the first session in the total ordered collection of matching sessions - * @param size a positive integer specifying how many matching sessions should be retrieved or {@code -1} if all of them should be retrieved - * @param sortBy an optional ({@code null} if no sorting is required) String of comma ({@code ,}) separated property names on which ordering should be performed, ordering - * elements according to the property order in the - * String, considering each in turn and moving on to the next one in case of equality of all preceding ones. Each property name is optionally followed by - * a column ({@code :}) and an order specifier: {@code asc} or {@code desc}. - * @return a {@link PartialList} of matching sessions + * @param profileId the profile identifier + * @param query optional full-text filter, or {@code null} for all sessions + * @param offset zero-based index of the first result + * @param size maximum number of results to return, or {@code -1} for all matches + * @param sortBy optional comma-separated sort fields with optional {@code :asc} or {@code :desc} + * @return a paged list of matching sessions */ @GET @Path("/{profileId}/sessions") @@ -249,10 +268,10 @@ public PartialList getProfileSessions(@PathParam("profileId") String pr } /** - * Retrieves the list of segment metadata for the segments the specified profile is a member of. + * Returns segment metadata for segments that contain the given profile. * - * @param profileId the identifier of the profile for which we want to retrieve the segment metadata - * @return the (possibly empty) list of segment metadata for the segments the specified profile is a member of + * @param profileId the profile identifier + * @return segment metadata for memberships of this profile */ @GET @Path("/{profileId}/segments") @@ -262,10 +281,10 @@ public List getProfileSegments(@PathParam("profileId") String profileI } /** - * TODO + * Returns the property-type mapping for the given source property type ID. * - * @param fromPropertyTypeId fromPropertyTypeId - * @return property type mapping + * @param fromPropertyTypeId the source property type identifier + * @return the mapped target property type identifier */ @GET @Path("/properties/mappings/{fromPropertyTypeId}") @@ -274,10 +293,10 @@ public String getPropertyTypeMapping(@PathParam("fromPropertyTypeId") String fro } /** - * Retrieves {@link Persona} matching the specified query. + * Returns personas matching the given query. * - * @param query a {@link Query} specifying which elements to retrieve - * @return a {@link PartialList} of Persona instances matching the specified query + * @param query the search query + * @return a paged list of matching personas */ @POST @Path("/personas/search") @@ -286,10 +305,10 @@ public PartialList getPersonas(Query query) { } /** - * Retrieves the {@link Persona} identified by the specified identifier. + * Returns the persona with the given ID. * - * @param personaId the identifier of the persona to retrieve - * @return the persona identified by the specified identifier or {@code null} if no such persona exists + * @param personaId the persona identifier + * @return the persona, or {@code null} when it does not exist */ @GET @Path("/personas/{personaId}") @@ -298,10 +317,10 @@ public Persona loadPersona(@PathParam("personaId") String personaId) { } /** - * Retrieves the persona identified by the specified identifier and all its associated sessions + * Returns the persona with the given ID and all associated sessions. * - * @param personaId the identifier of the persona to retrieve - * @return a {@link PersonaWithSessions} instance with the persona identified by the specified identifier and all its associated sessions + * @param personaId the persona identifier + * @return the persona and its sessions */ @GET @Path("/personasWithSessions/{personaId}") @@ -310,10 +329,10 @@ public PersonaWithSessions loadPersonaWithSessions(@PathParam("personaId") Strin } /** - * Save the posted persona with its sessions + * Saves the posted persona together with its sessions. * - * @param personaWithSessions the persona to save with its sessions. - * @return a {@link PersonaWithSessions} instance with the persona identified by the specified identifier and all its associated sessions + * @param personaWithSessions the persona and sessions to persist + * @return the saved persona and sessions */ @POST @Path("/personasWithSessions") @@ -359,17 +378,13 @@ public Persona createPersona(@PathParam("personaId") String personaId) { } /** - * Retrieves the sessions associated with the persona identified by the specified identifier, ordered according to the specified {@code sortBy} String and and paged: only - * {@code size} of them are retrieved, starting with the {@code offset}-th one. + * Returns sessions for the persona with the given ID. * - * @param personaId the persona id - * @param offset zero or a positive integer specifying the position of the first session in the total ordered collection of matching sessions - * @param size a positive integer specifying how many matching sessions should be retrieved or {@code -1} if all of them should be retrieved - * @param sortBy an optional ({@code null} if no sorting is required) String of comma ({@code ,}) separated property names on which ordering should be performed, ordering - * elements according to the property order in the - * String, considering each in turn and moving on to the next one in case of equality of all preceding ones. Each property name is optionally followed by - * a column ({@code :}) and an order specifier: {@code asc} or {@code desc}. - * @return a {@link PartialList} of sessions for the persona identified by the specified identifier + * @param personaId the persona identifier + * @param offset zero-based index of the first result + * @param size maximum number of results to return, or {@code -1} for all matches + * @param sortBy optional comma-separated sort fields with optional {@code :asc} or {@code :desc} + * @return a paged list of persona sessions */ @GET @Path("/personas/{personaId}/sessions") @@ -381,11 +396,11 @@ public PartialList getPersonaSessions(@PathParam("personaId") St } /** - * Retrieves the session identified by the specified identifier. + * Returns the session with the given ID. * - * @param sessionId the identifier of the session to be retrieved - * @return the session identified by the specified identifier - * @throws ParseException if the date hint cannot be parsed as a proper {@link Date} object + * @param sessionId the session identifier + * @return the session + * @throws ParseException if a stored date hint cannot be parsed */ @GET @Path("/sessions/{sessionId}") @@ -406,9 +421,9 @@ public Session saveSession(Session session) { } /** - * Delete the session by specifying its unique identifier. + * Deletes the session with the given ID. * - * @param sessionId the session identifier for the session to delete + * @param sessionId the session identifier */ @DELETE @Path("/sessions/{sessionId}") @@ -417,20 +432,17 @@ public void deleteSession(@PathParam("sessionId") String sessionId) { } /** - * Retrieves {@link Event}s for the {@link Session} identified by the provided session identifier, matching any of the provided event types, - * ordered according to the specified {@code sortBy} String and paged: only {@code size} of them are retrieved, starting with the {@code offset}-th one. - * If a {@code query} is provided, a full text search is performed on the matching events to further filter them. + * Returns events for the session with the given ID. + *

+ * Results can be filtered by event type, optional full-text query, and paging parameters. * - * @param sessionId the identifier of the user session we're considering - * @param eventTypes an array of event type names; the events to retrieve should at least match one of these - * @param query a String to perform full text filtering on events matching the other conditions - * @param offset zero or a positive integer specifying the position of the first event in the total ordered collection of matching events - * @param size a positive integer specifying how many matching events should be retrieved or {@code -1} if all of them should be retrieved - * @param sortBy an optional ({@code null} if no sorting is required) String of comma ({@code ,}) separated property names on which ordering should be performed, ordering - * elements according to the property order in - * the String, considering each in turn and moving on to the next one in case of equality of all preceding ones. Each property name is optionally followed by - * a column ({@code :}) and an order specifier: {@code asc} or {@code desc}. - * @return a {@link PartialList} of matching events + * @param sessionId the session identifier + * @param eventTypes event types to include; an event must match at least one + * @param query optional full-text filter + * @param offset zero-based index of the first result + * @param size maximum number of results to return, or {@code -1} for all matches + * @param sortBy optional comma-separated sort fields with optional {@code :asc} or {@code :desc} + * @return a paged list of matching events */ @GET @Path("/sessions/{sessionId}/events") @@ -443,26 +455,40 @@ public PartialList getSessionEvents(@PathParam("sessionId") String sessio return eventService.searchEvents(sessionId, eventTypes, query, offset, size, sortBy); } + /** + * Finds sessions for the given profile. + * + * @param profileId the profile identifier + * @return the matching sessions, or {@code null} when not implemented + */ public PartialList findProfileSessions(String profileId) { return null; } + /** + * Tests whether a condition matches the given profile and session. + * + * @param condition the condition to test + * @param profile the profile + * @param session the session + * @return {@code true} when the condition matches + */ public boolean matchCondition(Condition condition, Profile profile, Session session) { return profileService.matchCondition(condition, profile, session); } /** - * Retrieves the existing property types for the specified type as defined by the Item subclass public field {@code ITEM_TYPE} and with the specified tag or system tag. + * Returns property types already in use for the given item type and tag. * - * TODO: move to a different class + * Property-type lookup helpers that may move to a dedicated endpoint in a future release. * - * @param tag the tag we're interested in - * @param isSystemTag if we should look in system tags instead of tags - * @param itemType the String representation of the item type we want to retrieve the count of, as defined by its class' {@code ITEM_TYPE} field - * @param language the value of the {@code Accept-Language} header to specify in which locale the properties description should be returned TODO unused - * @param response the http response object - * @return all property types defined for the specified item type and with the specified tag - * @throws IOException if there was an error sending the response + * @param tag the tag or system tag to match + * @param isSystemTag whether {@code tag} is a system tag + * @param itemType the item type name from the class {@code ITEM_TYPE} field + * @param language the requested locale for property descriptions (currently unused) + * @param response the HTTP response used to signal missing query parameters + * @return matching property types, or {@code null} when required parameters are missing + * @throws IOException if sending the error response fails */ @GET @Path("/existingProperties") @@ -481,12 +507,12 @@ public Collection getExistingProperties(@QueryParam("tag") String } /** - * Retrieves all known property types. + * Returns all known property types grouped by target. * - * TODO: move to a different class + * Property-type lookup helpers that may move to a dedicated endpoint in a future release. * - * @param language the value of the {@code Accept-Language} header to specify in which locale the properties description should be returned TODO unused - * @return a Map associating targets as keys to related {@link PropertyType}s + * @param language the requested locale for property descriptions (currently unused) + * @return target name to property type mappings */ @GET @Path("/properties") @@ -495,13 +521,13 @@ public Map> getPropertyTypes(@HeaderParam("Acce } /** - * Retrieves the property type associated with the specified property ID. + * Returns the property type for the given property ID. * - * TODO: move to a different class + * Property-type lookup helpers that may move to a dedicated endpoint in a future release. * - * @param propertyId the property ID for which we want to retrieve the associated property type - * @param language the value of the {@code Accept-Language} header to specify in which locale the properties description should be returned TODO unused - * @return the property type associated with the specified ID + * @param propertyId the property identifier + * @param language the requested locale for property descriptions (currently unused) + * @return the property type */ @GET @Path("/properties/{propertyId}") @@ -510,13 +536,13 @@ public PropertyType getPropertyType(@PathParam("propertyId") String propertyId, } /** - * Retrieves all the property types associated with the specified target. + * Returns property types for the given target. * - * TODO: move to a different class + * Property-type lookup helpers that may move to a dedicated endpoint in a future release. * - * @param target the target for which we want to retrieve the associated property types - * @param language the value of the {@code Accept-Language} header to specify in which locale the properties description should be returned TODO unused - * @return a collection of all the property types associated with the specified target + * @param target the property target name + * @param language the requested locale for property descriptions (currently unused) + * @return property types for the target */ @GET @Path("/properties/targets/{target}") @@ -525,14 +551,14 @@ public Collection getPropertyTypesByTarget(@PathParam("target") St } /** - * Retrieves all property types with the specified tags. + * Returns property types that match any of the given tags. * - * TODO: move to a different class - * TODO: passing a list of tags via a comma-separated list is not very RESTful + * Property-type lookup helpers that may move to a dedicated endpoint in a future release. + * Tags are passed as a comma-separated path segment for backward compatibility. * - * @param tags a comma-separated list of tag identifiers - * @param language the value of the {@code Accept-Language} header to specify in which locale the properties description should be returned TODO unused - * @return a Set of the property types with the specified tag + * @param tags comma-separated tag identifiers + * @param language the requested locale for property descriptions (currently unused) + * @return matching property types */ @GET @Path("/properties/tags/{tags}") @@ -546,14 +572,14 @@ public Collection getPropertyTypeByTag(@PathParam("tags") String t } /** - * Retrieves all property types with the specified tags. + * Returns property types that match any of the given system tags. * - * TODO: move to a different class - * TODO: passing a list of tags via a comma-separated list is not very RESTful + * Property-type lookup helpers that may move to a dedicated endpoint in a future release. + * Tags are passed as a comma-separated path segment for backward compatibility. * - * @param tags a comma-separated list of tag identifiers - * @param language the value of the {@code Accept-Language} header to specify in which locale the properties description should be returned TODO unused - * @return a Set of the property types with the specified tag + * @param tags comma-separated system tag identifiers + * @param language the requested locale for property descriptions (currently unused) + * @return matching property types */ @GET @Path("/properties/systemTags/{tags}") @@ -569,7 +595,7 @@ public Collection getPropertyTypeBySystemTag(@PathParam("tags") St /** * Persists the specified property type in the context server. * - * TODO: move to a different class + * Property-type lookup helpers that may move to a dedicated endpoint in a future release. * * @param property the property type to persist * @return {@code true} if the property type was properly created, {@code false} otherwise (for example, if the property type already existed @@ -583,7 +609,7 @@ public boolean setPropertyType(PropertyType property) { /** * Persists the specified properties type in the context server. * - * TODO: move to a different class + * Property-type lookup helpers that may move to a dedicated endpoint in a future release. * * @param properties the properties type to persist * @return {@code true} if the property type was properly created, {@code false} otherwise (for example, if the property type already existed @@ -601,7 +627,7 @@ public boolean setPropertyTypes(List properties) { /** * Deletes the property type identified by the specified identifier. * - * TODO: move to a different class + * Property-type lookup helpers that may move to a dedicated endpoint in a future release. * * @param propertyId the identifier of the property type to delete * @return {@code true} if the property type was properly deleted, {@code false} otherwise @@ -613,10 +639,10 @@ public boolean deleteProperty(@PathParam("propertyId") String propertyId) { } /** - * Retrieves sessions matching the specified query. + * Returns sessions matching the given query. * - * @param query a {@link Query} specifying which elements to retrieve - * @return a {@link PartialList} of sessions matching the specified query + * @param query the search query + * @return a paged list of matching sessions */ @POST @Path("/search/sessions") @@ -624,6 +650,13 @@ public PartialList searchSession(Query query) { return profileService.searchSessions(query); } + /** + * Adds an alias to a profile. + * + * @param profileId the profile identifier + * @param aliasId the alias identifier + * @param headerClientID optional client identifier from the request header + */ @POST @Path("/{profileId}/aliases/{aliasId}") public void addAliasToProfile(final @PathParam("profileId") String profileId, @@ -633,6 +666,13 @@ public void addAliasToProfile(final @PathParam("profileId") String profileId, profileService.addAliasToProfile(profileId, aliasId, clientId); } + /** + * Removes an alias from a profile. + * + * @param profileId the profile identifier + * @param aliasId the alias identifier + * @param headerClientID optional client identifier from the request header + */ @DELETE @Path("/{profileId}/aliases/{aliasId}") public void removeAliasFromProfile(final @PathParam("profileId") String profileId, @@ -642,6 +682,15 @@ public void removeAliasFromProfile(final @PathParam("profileId") String profileI profileService.removeAliasFromProfile(profileId, aliasId, clientId); } + /** + * Lists aliases for the given profile. + * + * @param profileId the profile identifier + * @param offset pagination offset + * @param size page size + * @param sortBy optional sort field + * @return the matching profile aliases + */ @GET @Path("/{profileId}/aliases") public PartialList listAliasesByProfileId(final @PathParam("profileId") String profileId, diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/QueryServiceEndPoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/QueryServiceEndPoint.java index 56168259d..d52844361 100644 --- a/rest/src/main/java/org/apache/unomi/rest/endpoints/QueryServiceEndPoint.java +++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/QueryServiceEndPoint.java @@ -36,7 +36,7 @@ import java.util.Map; /** - * A JAX-RS endpoint to perform queries against context-server data. + * JAX-RS endpoint for aggregate counts, metrics, and conditional item counts. */ @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @@ -55,21 +55,30 @@ public class QueryServiceEndPoint { @Reference private LocalizationHelper localizationHelper; + /** + * Sets the query service. + * + * @param queryService the query service + */ public void setQueryService(QueryService queryService) { this.queryService = queryService; } + /** + * Sets the localization helper. + * + * @param localizationHelper the localization helper + */ public void setLocalizationHelper(LocalizationHelper localizationHelper) { this.localizationHelper = localizationHelper; } /** - * Retrieves the number of items with the specified type as defined by the Item subclass public field {@code ITEM_TYPE} and aggregated by possible values of the specified - * property. + * Returns item counts grouped by distinct values of the given property. * - * @param type the String representation of the item type we want to retrieve the count of, as defined by its class' {@code ITEM_TYPE} field - * @param property the property we're aggregating on, i.e. for each possible value of this property, we are counting how many items of the specified type have that value - * @return a Map associating a specific value of the property to the cardinality of items with that value + * @param type the item type name from the class {@code ITEM_TYPE} field + * @param property the property whose distinct values form aggregation buckets + * @return property value to item count mappings * @see Item Item for a discussion of {@code ITEM_TYPE} */ @GET @@ -79,18 +88,15 @@ public Map getAggregate(@PathParam("type") String type, @PathParam } /** - * TODO: rework, this method is confusing since it either behaves like {@link #getAggregate(String, String)} if query is null but completely differently if it isn't - * - * Retrieves the number of items with the specified type as defined by the Item subclass public field {@code ITEM_TYPE} and aggregated by possible values of the specified - * property or, if the specified query is not {@code null}, perform that aggregate query. - * Also return the global count of document matching the {@code ITEM_TYPE} if you don't use {@code optimizedQuery} or set it to false, - * otherwise if {@code optimizedQuery} is set to true then it won't return the global count but the query will be executed much faster. + * Returns property-value counts for an item type, optionally using an aggregate query. + *

+ * When {@code optimizedQuery} is {@code true}, the global document count is omitted for faster execution. * - * @param type the String representation of the item type we want to retrieve the count of, as defined by its class' {@code ITEM_TYPE} field - * @param property the property we're aggregating on, i.e. for each possible value of this property, we are counting how many items of the specified type have that value - * @param aggregateQuery the {@link AggregateQuery} specifying the aggregation that should be performed - * @param optimizedQuery the {@code optimizedQuery} specifying if we should optimized the aggregate query or not - * @return a Map associating a specific value of the property to the cardinality of items with that value + * @param type the item type name from the class {@code ITEM_TYPE} field + * @param property the property whose distinct values form aggregation buckets + * @param optimizedQuery whether to use the optimized aggregate path + * @param aggregateQuery optional aggregate query constraints + * @return property value to item count mappings * @see Item Item for a discussion of {@code ITEM_TYPE} */ @POST @@ -105,15 +111,13 @@ public Map getAggregate(@PathParam("type") String type, @PathParam } /** - * Retrieves the specified metrics for the specified field of items of the specified type as defined by the Item subclass public field {@code ITEM_TYPE} and matching the - * specified {@link Condition}. + * Returns numeric metrics for a field on items that match the given condition. * - * @param condition the condition the items must satisfy - * @param metricsType a String specifying which metrics should be computed, separated by a slash ({@code /}) (possible values: {@code sum} for the sum of the - * values, {@code avg} for the average of the values, {@code min} for the minimum value and {@code max} for the maximum value) - * @param property the name of the field for which the metrics should be computed - * @param type the String representation of the item type we want to retrieve the count of, as defined by its class' {@code ITEM_TYPE} field - * @return a Map associating computed metric name as key to its associated value + * @param condition the condition matching items must satisfy + * @param metricsType slash-separated metric names ({@code sum}, {@code avg}, {@code min}, {@code max}) + * @param property the numeric field to aggregate + * @param type the item type name from the class {@code ITEM_TYPE} field + * @return metric name to computed value mappings * @see Item Item for a discussion of {@code ITEM_TYPE} */ @POST @@ -123,16 +127,13 @@ public Map getMetric(@PathParam("type") String type, @PathParam( } /** - * Retrieves the number of items of the specified type as defined by the Item subclass public field {@code ITEM_TYPE} and matching the specified {@link Condition}. + * Returns how many items of the given type match the condition. * - * @param condition the condition the items must satisfy - * @param validate optional parameter, in case of draft condition that have missing required parameters an IllegalArgumentException is throw - * and this end point will return status code 400, to avoid that you can set validate to false. - * @param type the String representation of the item type we want to retrieve the count of, as defined by its class' {@code ITEM_TYPE} field - * @param response the httpServletResponse - * @return the number of items of the specified type. - * 0 and status code 400 in case of IllegalArgumentException (bad condition) and validate null or true - * 0 and status code 200 in case of IllegalArgumentException (bad condition) and validate false + * @param condition the condition matching items must satisfy + * @param validate when {@code true} or omitted, invalid draft conditions return HTTP 400; when {@code false}, returns 0 with HTTP 200 + * @param type the item type name from the class {@code ITEM_TYPE} field + * @param response the HTTP response used to set status on validation failure + * @return the matching item count, or {@code 0} when validation fails * @see Item Item for a discussion of {@code ITEM_TYPE} */ @POST diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/RulesServiceEndPoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/RulesServiceEndPoint.java index 657ea7bc2..a908d5373 100644 --- a/rest/src/main/java/org/apache/unomi/rest/endpoints/RulesServiceEndPoint.java +++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/RulesServiceEndPoint.java @@ -52,18 +52,28 @@ public class RulesServiceEndPoint { @Reference private RulesService rulesService; + /** + * Creates the rules service endpoint. + */ public RulesServiceEndPoint() { LOGGER.info("Initializing rule service endpoint..."); } + /** + * Sets the rules service. + * + * @param rulesService the rules service + */ public void setRulesService(RulesService rulesService) { this.rulesService = rulesService; } /** - * Retrieves the metadata for all known rules. - * Note that it only includes the rules in memory, not those persisted in storage. - * @return the Set of known metadata + * Returns metadata for all in-memory rules. + *

+ * Note that this includes only rules currently loaded in memory, not every rule in storage. + * + * @return known rule metadata */ @GET @Path("/") @@ -83,9 +93,9 @@ public void setRule(Rule rule) { } /** - * Retrieves the rule statistics for all known rules. + * Returns execution statistics for all known rules. * - * @return a map that contains the rule key as a key and as the value a @RuleStatistics object. + * @return rule ID to statistics mappings */ @GET @Path("/statistics") @@ -94,7 +104,7 @@ public Map getAllRuleStatistics() { } /** - * Deletes all the rule statistics, which basically resets them to 0. + * Resets execution statistics for all rules to zero. */ @DELETE @Path("/statistics") @@ -103,10 +113,10 @@ public void resetAllRuleStatistics() { } /** - * Retrieves rule metadatas for rules matching the specified {@link Query}. + * Returns rule metadata matching the given query. * - * @param query the query the rules which metadata we want to retrieve must match - * @return a {@link PartialList} of rules metadata for the rules matching the specified query + * @param query the query rules must match + * @return a paged list of matching rule metadata */ @POST @Path("/query") @@ -115,10 +125,10 @@ public PartialList getRuleMetadatas(Query query) { } /** - * Retrieves rule details for rules matching the specified query. + * Returns full rule definitions matching the given query. * - * @param query the query specifying which rules to retrieve - * @return a {@link PartialList} of rule details for the rules matching the specified query + * @param query the query specifying which rules to include + * @return a paged list of matching rules */ @POST @Path("/query/detailed") @@ -127,10 +137,10 @@ public PartialList getRuleDetails(Query query) { } /** - * Retrieves the rule identified by the specified identifier. + * Returns the rule with the given ID. * - * @param ruleId the identifier of the rule we want to retrieve - * @return the rule identified by the specified identifier or {@code null} if no such rule exists. + * @param ruleId the rule identifier + * @return the rule, or {@code null} when it does not exist */ @GET @Path("/{ruleId}") @@ -139,10 +149,10 @@ public Rule getRule( @PathParam("ruleId") String ruleId) { } /** - * Retrieves the statistics for the rule with the specified identifier + * Returns execution statistics for the rule with the given ID. * - * @param ruleId the identifier of the rule we want to retrieve - * @return the statistics for the specified rule or {@code null} if no such rule exists. + * @param ruleId the rule identifier + * @return the rule statistics, or {@code null} when the rule does not exist */ @GET @Path("/{ruleId}/statistics") @@ -162,7 +172,7 @@ public void removeRule(@PathParam("ruleId") String ruleId) { } /** - * TODO: remove + * Deprecated maintenance endpoint kept for backward compatibility. * * @deprecated As of version 1.1.0-incubating, not needed anymore */ diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/ScopeServiceEndPoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/ScopeServiceEndPoint.java index 7e07b803d..cbee62b60 100644 --- a/rest/src/main/java/org/apache/unomi/rest/endpoints/ScopeServiceEndPoint.java +++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/ScopeServiceEndPoint.java @@ -45,18 +45,26 @@ public class ScopeServiceEndPoint { @Reference private ScopeService scopeService; + /** + * Creates the scope service endpoint. + */ public ScopeServiceEndPoint() { LOGGER.info("Initializing scope service endpoint..."); } + /** + * Sets the scope service. + * + * @param scopeService the scope service + */ public void setScopeService(ScopeService scopeService) { this.scopeService = scopeService; } /** - * Retrieves all known scopes. + * Returns all configured scopes. * - * @return a List of the scopes + * @return all known scopes */ @GET @Path("/") @@ -67,6 +75,7 @@ public void setScopeService(ScopeService scopeService) { * Persists the specified scope. * * @param scope the scope to be persisted + * @return an empty success response */ @POST @Path("/") @@ -76,10 +85,10 @@ public Response save(Scope scope) { } /** - * Retrieves the scope identified by the specified identifier. + * Returns the scope with the given ID. * - * @param scopeId the identifier of the scope we want to retrieve - * @return the scope identified by the specified identifier or {@code null} if no such scope exists. + * @param scopeId the scope identifier + * @return the scope, or {@code null} when it does not exist */ @GET @Path("/{scopeId}") diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/ScoringServiceEndPoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/ScoringServiceEndPoint.java index 471e419e0..8d78bf84b 100644 --- a/rest/src/main/java/org/apache/unomi/rest/endpoints/ScoringServiceEndPoint.java +++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/ScoringServiceEndPoint.java @@ -35,7 +35,7 @@ import java.util.List; /** - * A JAX-RS endpoint to manage {@link Scoring}s + * JAX-RS endpoint for managing {@link Scoring} definitions and their dependents. */ @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @@ -52,24 +52,29 @@ public class ScoringServiceEndPoint { @Reference private SegmentService segmentService; + /** + * Creates the scoring service endpoint. + */ public ScoringServiceEndPoint() { LOGGER.info("Initializing scoring service endpoint..."); } + /** + * Sets the segment service. + * + * @param segmentService the segment service + */ public void setSegmentService(SegmentService segmentService) { this.segmentService = segmentService; } /** - * Retrieves the set of all scoring metadata. - * @param offset zero or a positive integer specifying the position of the first element in the total ordered collection of matching elements - * @param size a positive integer specifying how many matching elements should be retrieved or {@code -1} if all of them should be retrieved - * @param sortBy an optional ({@code null} if no sorting is required) String of comma ({@code ,}) separated property names on which ordering should be performed, ordering - * elements according to the property order in the - * String, considering each in turn and moving on to the next one in case of equality of all preceding ones. Each property name is optionally followed by - * a column ({@code :}) and an order specifier: {@code asc} or {@code desc}. + * Returns scoring metadata with paging and optional sorting. * - * @return the set of all scoring metadata + * @param offset zero-based index of the first result + * @param size maximum number of results to return, or {@code -1} for all matches + * @param sortBy optional comma-separated sort fields with optional {@code :asc} or {@code :desc} + * @return matching scoring metadata */ @GET @Path("/") @@ -78,10 +83,10 @@ public List getScoringMetadatas(@QueryParam("offset") @DefaultValue("0 } /** - * Retrieves the set of scoring metadata for scorings matching the specified query. + * Returns scoring metadata matching the given query. * - * @param query the query the scorings must match for their metadata to be retrieved - * @return the set of scoring metadata for scorings matching the specified query + * @param query the query scorings must match + * @return a paged list of matching scoring metadata */ @POST @Path("/query") @@ -90,10 +95,10 @@ public PartialList getScoringMetadatas(Query query) { } /** - * Retrieves the scoring identified by the specified identifier. + * Returns the scoring definition with the given ID. * - * @param scoringId the identifier of the scoring to be retrieved - * @return the scoring identified by the specified identifier or {@code null} if no such scoring exists + * @param scoringId the scoring identifier + * @return the scoring, or {@code null} when it does not exist */ @GET @Path("/{scoringID}") @@ -145,11 +150,12 @@ public DependentMetadata removeScoringDefinition(@PathParam("scoringID") String } /** - * Retrieves the list of Segment and Scoring metadata depending on the specified scoring. - * A segment or scoring is depending on a segment if it includes a scoringCondition with a test on this scoring. + * Returns segment and scoring metadata that depend on the given scoring. + *

+ * A dependent definition includes a scoring condition that references this scoring. * - * @param scoringId the segment identifier - * @return a list of Segment/Scoring Metadata depending on the specified scoring + * @param scoringId the scoring identifier + * @return metadata for dependent segments and scorings */ @GET @Path("/{scoringID}/impacted") @@ -158,7 +164,7 @@ public DependentMetadata getScoringDependentMetadata(@PathParam("scoringID") Str } /** - * TODO: remove + * Deprecated maintenance endpoint kept for backward compatibility. * * @deprecated As of version 1.1.0-incubating, not needed anymore */ diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/SegmentServiceEndPoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/SegmentServiceEndPoint.java index 24f4d8878..ab4529011 100644 --- a/rest/src/main/java/org/apache/unomi/rest/endpoints/SegmentServiceEndPoint.java +++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/SegmentServiceEndPoint.java @@ -52,26 +52,30 @@ public class SegmentServiceEndPoint { @Reference private SegmentService segmentService; + /** + * Creates the segment service endpoint. + */ public SegmentServiceEndPoint() { LOGGER.info("Initializing segment service endpoint..."); } + /** + * Sets the segment service. + * + * @param segmentService the segment service + */ public void setSegmentService(SegmentService segmentService) { this.segmentService = segmentService; } /** - * Retrieves a list of profiles matching the conditions defined by the segment identified by the specified identifier, ordered according to the specified {@code sortBy} - * String and and paged: only {@code size} of them are retrieved, starting with the {@code offset}-th one. + * Returns profiles that match the segment with the given ID. * - * @param segmentId the identifier of the segment for which we want to retrieve matching profiles - * @param offset zero or a positive integer specifying the position of the first element in the total ordered collection of matching elements - * @param size a positive integer specifying how many matching elements should be retrieved or {@code -1} if all of them should be retrieved - * @param sortBy an optional ({@code null} if no sorting is required) String of comma ({@code ,}) separated property names on which ordering should be performed, ordering - * elements according to the property order in the - * String, considering each in turn and moving on to the next one in case of equality of all preceding ones. Each property name is optionally followed by - * a column ({@code :}) and an order specifier: {@code asc} or {@code desc}. - * @return a {@link PartialList} of profiles matching the specified segment + * @param segmentId the segment identifier + * @param offset zero-based index of the first result + * @param size maximum number of results to return, or {@code -1} for all matches + * @param sortBy optional comma-separated sort fields with optional {@code :asc} or {@code :desc} + * @return a paged list of matching profiles */ @GET @Path("/{segmentID}/match") @@ -80,10 +84,10 @@ public PartialList getMatchingIndividuals(@PathParam("segmentID") Strin } /** - * Retrieves the number of profiles matching the conditions defined by the segment identified by the specified identifier. + * Returns how many profiles match the segment with the given ID. * - * @param segmentId the identifier of the segment for which we want to retrieve matching profiles - * @return the number of profiles matching the conditions defined by the segment identified by the specified identifier + * @param segmentId the segment identifier + * @return the number of matching profiles */ @GET @Path("/{segmentID}/count") @@ -105,16 +109,12 @@ public Boolean isProfileInSegment(@PathParam("profile") Profile profile, @PathPa } /** - * Retrieves the 50 first segment metadatas. + * Returns segment metadata with paging and optional sorting. * - * @param offset zero or a positive integer specifying the position of the first element in the total ordered collection of matching elements - * @param size a positive integer specifying how many matching elements should be retrieved or {@code -1} if all of them should be retrieved - * @param sortBy an optional ({@code null} if no sorting is required) String of comma ({@code ,}) separated property names on which ordering should be performed, ordering - * elements according to the property order in the - * String, considering each in turn and moving on to the next one in case of equality of all preceding ones. Each property name is optionally followed by - * a column ({@code :}) and an order specifier: {@code asc} or {@code desc}. - * - * @return a List of the 50 first segment metadata + * @param offset zero-based index of the first result + * @param size maximum number of results to return, or {@code -1} for all matches + * @param sortBy optional comma-separated sort fields with optional {@code :asc} or {@code :desc} + * @return matching segment metadata */ @GET @Path("/") @@ -123,11 +123,12 @@ public List getSegmentMetadatas(@QueryParam("offset") @DefaultValue("0 } /** - * Retrieves the list of Segment and Scoring metadata depending on the specified segment. - * A segment or scoring is depending on a segment if it includes a profileSegmentCondition with a test on this segment. + * Returns segment and scoring metadata that depend on the given segment. + *

+ * A dependent definition includes a profile-segment condition that references this segment. * * @param segmentId the segment identifier - * @return a list of Segment/Scoring Metadata depending on the specified segment + * @return metadata for dependent segments and scorings */ @GET @Path("/{segmentID}/impacted") @@ -147,10 +148,10 @@ public void setSegmentDefinition(Segment segment) { } /** - * Retrieves the metadata for segments matching the specified {@link Query}. + * Returns segment metadata matching the given query. * - * @param query the query that the segments must match for their metadata to be retrieved - * @return a {@link PartialList} of segment metadata + * @param query the query segments must match + * @return a paged list of matching segment metadata */ @POST @Path("/query") @@ -159,10 +160,10 @@ public PartialList getListMetadatas(Query query) { } /** - * Retrieves the segment identified by the specified identifier. + * Returns the segment definition with the given ID. * - * @param segmentId the identifier of the segment to be retrieved - * @return the segment identified by the specified identifier or {@code null} if no such segment exists + * @param segmentId the segment identifier + * @return the segment, or {@code null} when it does not exist */ @GET @Path("/{segmentID}") @@ -187,7 +188,7 @@ public DependentMetadata removeSegmentDefinition(@PathParam("segmentID") String } /** - * TODO: remove + * Deprecated maintenance endpoint kept for backward compatibility. * * @deprecated As of version 1.1.0-incubating, not needed anymore */ diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/TestEndPoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/TestEndPoint.java index ea013e12d..2d86ec0ec 100644 --- a/rest/src/main/java/org/apache/unomi/rest/endpoints/TestEndPoint.java +++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/TestEndPoint.java @@ -27,7 +27,9 @@ import javax.ws.rs.core.MediaType; /** - * @author Jerome Blanchard + * Simple health-style JAX-RS endpoint for manual connectivity checks. + * Returns plain text so load balancers or operators can verify the REST + * layer is reachable without exercising business services. */ @Produces(MediaType.TEXT_PLAIN + ";charset=UTF-8") @Path("/test") @@ -37,10 +39,18 @@ public class TestEndPoint { private static final Logger LOGGER = LoggerFactory.getLogger(TestEndPoint.class.getName()); + /** + * Creates the test endpoint. + */ public TestEndPoint() { LOGGER.info("TestEndPoint initialized."); } + /** + * Returns a simple health-check response. + * + * @return the ping response + */ @GET @Path("/ping") public String ping() { diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/UserListServiceEndPoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/UserListServiceEndPoint.java index bbb12496f..5d8e80ea8 100644 --- a/rest/src/main/java/org/apache/unomi/rest/endpoints/UserListServiceEndPoint.java +++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/UserListServiceEndPoint.java @@ -17,10 +17,6 @@ package org.apache.unomi.rest.endpoints; -/** - * Created by amidani on 24/03/2017. - */ - import org.apache.cxf.rs.security.cors.CrossOriginResourceSharing; import org.apache.unomi.api.Metadata; import org.apache.unomi.api.lists.UserList; @@ -35,7 +31,9 @@ import java.util.List; /** - * A JAX-RS endpoint to manage {@link UserList}s. + * JAX-RS endpoint for static {@link UserList} CRUD and membership management. + * Delegates to {@link UserListService} so marketers can maintain fixed audience + * lists used by campaigns and exports. */ @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @@ -52,25 +50,29 @@ public class UserListServiceEndPoint { @Reference private UserListService userListService; + /** + * Creates the user list service endpoint. + */ public UserListServiceEndPoint() { LOGGER.info("Initializing user lists service endpoint..."); } + /** + * Sets the user list service. + * + * @param userListService the user list service + */ public void setUserListService(UserListService userListService) { this.userListService = userListService; } /** - * Retrieves the 50 first {@link UserList} metadatas. - * - * @param offset zero or a positive integer specifying the position of the first element in the total ordered collection of matching elements - * @param size a positive integer specifying how many matching elements should be retrieved or {@code -1} if all of them should be retrieved - * @param sortBy an optional ({@code null} if no sorting is required) String of comma ({@code ,}) separated property names on which ordering should be performed, ordering - * elements according to the property order in the - * String, considering each in turn and moving on to the next one in case of equality of all preceding ones. Each property name is optionally followed by - * a column ({@code :}) and an order specifier: {@code asc} or {@code desc}. + * Returns user list metadata with paging and optional sorting. * - * @return a List of the 50 first {@link UserList} metadata + * @param offset zero-based index of the first result + * @param size maximum number of results to return, or {@code -1} for all matches + * @param sortBy optional comma-separated sort fields with optional {@code :asc} or {@code :desc} + * @return matching user list metadata */ @GET @Path("/") diff --git a/rest/src/main/java/org/apache/unomi/rest/exception/AbstractRestExceptionMapper.java b/rest/src/main/java/org/apache/unomi/rest/exception/AbstractRestExceptionMapper.java index cb26707c2..f16503d63 100644 --- a/rest/src/main/java/org/apache/unomi/rest/exception/AbstractRestExceptionMapper.java +++ b/rest/src/main/java/org/apache/unomi/rest/exception/AbstractRestExceptionMapper.java @@ -56,6 +56,13 @@ protected Response internalServerErrorResponse() { return jsonErrorResponse(Response.Status.INTERNAL_SERVER_ERROR, "internalServerError"); } + /** + * Builds a JSON error response with the given status and message key. + * + * @param status the HTTP status + * @param errorMessage the error message key + * @return the JSON error response + */ protected Response jsonErrorResponse(Response.Status status, String errorMessage) { Map body = new HashMap<>(); body.put(ERROR_MESSAGE_KEY, errorMessage); @@ -63,6 +70,7 @@ protected Response jsonErrorResponse(Response.Status status, String errorMessage } /** + * @param rootCause the throwable to inspect * @return {@code true} when the given root cause is a Jackson deserialization failure, i.e. a * client error (malformed/mistyped request body) rather than a genuine server fault. */ @@ -71,6 +79,7 @@ protected boolean isJsonDeserializationError(Throwable rootCause) { } /** + * @param throwable the throwable to inspect * @return {@code true} when the throwable represents invalid client input rejected by domain * validation (e.g. rule or segment condition checks), rather than a server fault. */ @@ -78,6 +87,12 @@ protected boolean isClientValidationError(Throwable throwable) { return throwable instanceof IllegalArgumentException || throwable instanceof BadSegmentConditionException; } + /** + * Returns the root cause of the given throwable. + * + * @param throwable the throwable to inspect + * @return the root cause, or {@code null} when the input is {@code null} + */ protected Throwable getRootCause(Throwable throwable) { if (throwable == null) { return null; @@ -93,6 +108,7 @@ protected Throwable getRootCause(Throwable throwable) { } /** + * @param throwable the throwable to inspect * @return the throwable's message, or its simple class name when no message is available. */ protected String messageOrType(Throwable throwable) { @@ -106,6 +122,8 @@ protected String messageOrType(Throwable throwable) { /** * Builds a sanitized "METHOD /path?query" description of the current request for logging. * Never throws: returns a placeholder when the request context cannot be resolved. + * + * @return the sanitized request context string */ protected String buildRequestContext() { StringBuilder context = new StringBuilder(); diff --git a/rest/src/main/java/org/apache/unomi/rest/exception/InvalidRequestException.java b/rest/src/main/java/org/apache/unomi/rest/exception/InvalidRequestException.java index d97495e6a..7bf517fa9 100644 --- a/rest/src/main/java/org/apache/unomi/rest/exception/InvalidRequestException.java +++ b/rest/src/main/java/org/apache/unomi/rest/exception/InvalidRequestException.java @@ -28,6 +28,7 @@ public class InvalidRequestException extends RuntimeException { /** * Build an invalid request + * * @param message message in the logs. It contains detailed information * @param responseMessage message in the response, sent to the browser, must be vague as possible. */ @@ -36,6 +37,11 @@ public InvalidRequestException(String message, String responseMessage) { this.responseMessage = responseMessage; } + /** + * Returns the message sent to the client. + * + * @return the client-facing response message + */ public String getResponseMessage() { return responseMessage; } diff --git a/rest/src/main/java/org/apache/unomi/rest/exception/InvalidRequestExceptionMapper.java b/rest/src/main/java/org/apache/unomi/rest/exception/InvalidRequestExceptionMapper.java index b2e09fea2..3159c0182 100644 --- a/rest/src/main/java/org/apache/unomi/rest/exception/InvalidRequestExceptionMapper.java +++ b/rest/src/main/java/org/apache/unomi/rest/exception/InvalidRequestExceptionMapper.java @@ -25,6 +25,9 @@ import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; +/** + * Maps {@link InvalidRequestException} to HTTP 400 responses. + */ @Provider @Component(service = ExceptionMapper.class) public class InvalidRequestExceptionMapper implements ExceptionMapper { diff --git a/rest/src/main/java/org/apache/unomi/rest/exception/RuntimeExceptionMapper.java b/rest/src/main/java/org/apache/unomi/rest/exception/RuntimeExceptionMapper.java index 3744dbf0a..ffc30bea9 100644 --- a/rest/src/main/java/org/apache/unomi/rest/exception/RuntimeExceptionMapper.java +++ b/rest/src/main/java/org/apache/unomi/rest/exception/RuntimeExceptionMapper.java @@ -25,6 +25,9 @@ import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; +/** + * Maps unhandled {@link RuntimeException} instances to JSON error responses. + */ @Provider @Component(service = ExceptionMapper.class) public class RuntimeExceptionMapper extends AbstractRestExceptionMapper implements ExceptionMapper { diff --git a/rest/src/main/java/org/apache/unomi/rest/exception/ValidationExceptionMapper.java b/rest/src/main/java/org/apache/unomi/rest/exception/ValidationExceptionMapper.java index b26023e40..7856cc304 100644 --- a/rest/src/main/java/org/apache/unomi/rest/exception/ValidationExceptionMapper.java +++ b/rest/src/main/java/org/apache/unomi/rest/exception/ValidationExceptionMapper.java @@ -26,6 +26,9 @@ import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; +/** + * Maps Bean Validation {@link javax.validation.ConstraintViolationException} to HTTP 400 responses. + */ @Provider @Component(service = ExceptionMapper.class) public class ValidationExceptionMapper implements ExceptionMapper { diff --git a/rest/src/main/java/org/apache/unomi/rest/models/EventCollectorResponse.java b/rest/src/main/java/org/apache/unomi/rest/models/EventCollectorResponse.java index 7b8ba7119..b442c17c1 100644 --- a/rest/src/main/java/org/apache/unomi/rest/models/EventCollectorResponse.java +++ b/rest/src/main/java/org/apache/unomi/rest/models/EventCollectorResponse.java @@ -21,9 +21,9 @@ import java.io.Serializable; /** - * Response model for the event collector endpoint. - * This class provides information about the result of event processing, including - * which entities were updated and tracing information. + * REST response returned after the events collector processes a batch. + * Reports bitwise change flags (session/profile updated, errors) and may include + * a {@link TraceNode} tree when request tracing is enabled for the call. */ public class EventCollectorResponse implements Serializable { /** @@ -48,6 +48,9 @@ public class EventCollectorResponse implements Serializable { */ private TraceNode requestTracing; + /** + * Creates an empty event collector response. + */ public EventCollectorResponse() { } diff --git a/rest/src/main/java/org/apache/unomi/rest/models/RESTActionType.java b/rest/src/main/java/org/apache/unomi/rest/models/RESTActionType.java index 97d2c1676..47d28b8d1 100644 --- a/rest/src/main/java/org/apache/unomi/rest/models/RESTActionType.java +++ b/rest/src/main/java/org/apache/unomi/rest/models/RESTActionType.java @@ -34,58 +34,128 @@ public class RESTActionType { private List parameters; protected Long version; + /** + * Returns the action type identifier. + * + * @return the action type identifier + */ public String getId() { return id; } + /** + * Sets the action type identifier. + * + * @param id the action type identifier + */ public void setId(String id) { this.id = id; } + /** + * Returns the action type name. + * + * @return the action type name + */ public String getName() { return name; } + /** + * Sets the action type name. + * + * @param name the action type name + */ public void setName(String name) { this.name = name; } + /** + * Returns the action type description. + * + * @return the action type description + */ public String getDescription() { return description; } + /** + * Sets the action type description. + * + * @param description the action type description + */ public void setDescription(String description) { this.description = description; } + /** + * Returns the action type tags. + * + * @return the action type tags + */ public Set getTags() { return tags; } + /** + * Sets the action type tags. + * + * @param tags the action type tags + */ public void setTags(Set tags) { this.tags = tags; } + /** + * Returns the action type system tags. + * + * @return the action type system tags + */ public Set getSystemTags() { return systemTags; } + /** + * Sets the action type system tags. + * + * @param systemTags the action type system tags + */ public void setSystemTags(Set systemTags) { this.systemTags = systemTags; } + /** + * Returns the action type parameters. + * + * @return the action type parameters + */ public List getParameters() { return parameters; } + /** + * Sets the action type parameters. + * + * @param parameters the action type parameters + */ public void setParameters(List parameters) { this.parameters = parameters; } + /** + * Returns the action type version. + * + * @return the action type version + */ public Long getVersion() { return version; } + /** + * Sets the action type version. + * + * @param version the action type version + */ public void setVersion(Long version) { this.version = version; } diff --git a/rest/src/main/java/org/apache/unomi/rest/models/RESTConditionType.java b/rest/src/main/java/org/apache/unomi/rest/models/RESTConditionType.java index 47bbde203..a08e8fede 100644 --- a/rest/src/main/java/org/apache/unomi/rest/models/RESTConditionType.java +++ b/rest/src/main/java/org/apache/unomi/rest/models/RESTConditionType.java @@ -33,61 +33,134 @@ public class RESTConditionType { private List parameters = new ArrayList(); protected Long version; + /** + * Creates an empty REST condition type. + */ public RESTConditionType() { } + /** + * Returns the condition type identifier. + * + * @return the condition type identifier + */ public String getId() { return id; } + /** + * Sets the condition type identifier. + * + * @param id the condition type identifier + */ public void setId(String id) { this.id = id; } + /** + * Returns the condition type name. + * + * @return the condition type name + */ public String getName() { return name; } + /** + * Sets the condition type name. + * + * @param name the condition type name + */ public void setName(String name) { this.name = name; } + /** + * Returns the condition type description. + * + * @return the condition type description + */ public String getDescription() { return description; } + /** + * Sets the condition type description. + * + * @param description the condition type description + */ public void setDescription(String description) { this.description = description; } + /** + * Returns the condition type tags. + * + * @return the condition type tags + */ public Set getTags() { return tags; } + /** + * Sets the condition type tags. + * + * @param tags the condition type tags + */ public void setTags(Set tags) { this.tags = tags; } + /** + * Returns the condition type system tags. + * + * @return the condition type system tags + */ public Set getSystemTags() { return systemTags; } + /** + * Sets the condition type system tags. + * + * @param systemTags the condition type system tags + */ public void setSystemTags(Set systemTags) { this.systemTags = systemTags; } + /** + * Returns the condition type parameters. + * + * @return the condition type parameters + */ public List getParameters() { return parameters; } + /** + * Sets the condition type parameters. + * + * @param parameters the condition type parameters + */ public void setParameters(List parameters) { this.parameters = parameters; } + /** + * Returns the condition type version. + * + * @return the condition type version + */ public Long getVersion() { return version; } + /** + * Sets the condition type version. + * + * @param version the condition type version + */ public void setVersion(Long version) { this.version = version; } diff --git a/rest/src/main/java/org/apache/unomi/rest/models/RESTParameter.java b/rest/src/main/java/org/apache/unomi/rest/models/RESTParameter.java index d6257c6f0..63b38b3b7 100644 --- a/rest/src/main/java/org/apache/unomi/rest/models/RESTParameter.java +++ b/rest/src/main/java/org/apache/unomi/rest/models/RESTParameter.java @@ -28,34 +28,74 @@ public class RESTParameter { private boolean multivalued = false; private Object defaultValue = null; + /** + * Returns the parameter identifier. + * + * @return the parameter identifier + */ public String getId() { return id; } + /** + * Sets the parameter identifier. + * + * @param id the parameter identifier + */ public void setId(String id) { this.id = id; } + /** + * Returns the parameter type. + * + * @return the parameter type + */ public String getType() { return type; } + /** + * Sets the parameter type. + * + * @param type the parameter type + */ public void setType(String type) { this.type = type; } + /** + * Returns whether the parameter accepts multiple values. + * + * @return {@code true} when the parameter is multivalued + */ public boolean isMultivalued() { return multivalued; } + /** + * Sets whether the parameter accepts multiple values. + * + * @param multivalued {@code true} when the parameter is multivalued + */ public void setMultivalued(boolean multivalued) { this.multivalued = multivalued; } + /** + * Returns the default parameter value. + * + * @return the default value + */ public Object getDefaultValue() { return defaultValue; } + /** + * Sets the default parameter value. + * + * @param defaultValue the default value + */ public void setDefaultValue(Object defaultValue) { this.defaultValue = defaultValue; } diff --git a/rest/src/main/java/org/apache/unomi/rest/models/RESTValueType.java b/rest/src/main/java/org/apache/unomi/rest/models/RESTValueType.java index a501e8be5..33368e151 100644 --- a/rest/src/main/java/org/apache/unomi/rest/models/RESTValueType.java +++ b/rest/src/main/java/org/apache/unomi/rest/models/RESTValueType.java @@ -31,37 +31,80 @@ public class RESTValueType { private String description; private Set tags; + /** + * Creates an empty REST value type. + */ public RESTValueType() { } + /** + * Returns the value type identifier. + * + * @return the value type identifier + */ public String getId() { return id; } + /** + * Sets the value type identifier. + * + * @param id the value type identifier + */ public void setId(String id) { this.id = id; } + /** + * Returns the value type name. + * + * @return the value type name + */ public String getName() { return name; } + /** + * Sets the value type name. + * + * @param name the value type name + */ public void setName(String name) { this.name = name; } + /** + * Returns the value type description. + * + * @return the value type description + */ public String getDescription() { return description; } + /** + * Sets the value type description. + * + * @param description the value type description + */ public void setDescription(String description) { this.description = description; } + /** + * Returns the value type tags. + * + * @return the value type tags + */ public Set getTags() { return tags; } + /** + * Sets the value type tags. + * + * @param tags the value type tags + */ public void setTags(Set tags) { this.tags = tags; } diff --git a/rest/src/main/java/org/apache/unomi/rest/security/RequiresRole.java b/rest/src/main/java/org/apache/unomi/rest/security/RequiresRole.java index fb06d79d4..57951618d 100644 --- a/rest/src/main/java/org/apache/unomi/rest/security/RequiresRole.java +++ b/rest/src/main/java/org/apache/unomi/rest/security/RequiresRole.java @@ -21,8 +21,16 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Declares the roles required to access a REST endpoint or method. + */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface RequiresRole { + /** + * Required role names. + * + * @return the required roles + */ String[] value(); } diff --git a/rest/src/main/java/org/apache/unomi/rest/security/SecurityFilter.java b/rest/src/main/java/org/apache/unomi/rest/security/SecurityFilter.java index 50327fad2..c2f59165d 100644 --- a/rest/src/main/java/org/apache/unomi/rest/security/SecurityFilter.java +++ b/rest/src/main/java/org/apache/unomi/rest/security/SecurityFilter.java @@ -37,6 +37,9 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; +/** + * Request filter that enforces role and tenant access rules on REST endpoints. + */ @Provider @Component(service = SecurityFilter.class) @Priority(Priorities.AUTHORIZATION) diff --git a/rest/src/main/java/org/apache/unomi/rest/server/ApiKeyRestMixIn.java b/rest/src/main/java/org/apache/unomi/rest/server/ApiKeyRestMixIn.java index 25fa10f13..53cfeb25b 100644 --- a/rest/src/main/java/org/apache/unomi/rest/server/ApiKeyRestMixIn.java +++ b/rest/src/main/java/org/apache/unomi/rest/server/ApiKeyRestMixIn.java @@ -25,6 +25,9 @@ */ public abstract class ApiKeyRestMixIn { + /** + * Creates the API key REST mixin. + */ public ApiKeyRestMixIn() { } @JsonIgnore abstract String getKeyHash(); diff --git a/rest/src/main/java/org/apache/unomi/rest/server/RestServer.java b/rest/src/main/java/org/apache/unomi/rest/server/RestServer.java index b1ad21e97..788be2537 100644 --- a/rest/src/main/java/org/apache/unomi/rest/server/RestServer.java +++ b/rest/src/main/java/org/apache/unomi/rest/server/RestServer.java @@ -63,6 +63,9 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; +/** + * OSGi component that assembles and refreshes the JAX-RS server from registered endpoints. + */ @Component public class RestServer { @@ -92,46 +95,91 @@ public class RestServer { private static final QName UNOMI_REST_SERVER_END_POINT_NAME = new QName("http://rest.unomi.apache.org/", "UnomiRestServerEndPoint"); + /** + * Sets the the schema service. + * + * @param schemaService the schema service + */ @Reference(cardinality = ReferenceCardinality.MANDATORY) public void setSchemaService(SchemaService schemaService) { this.schemaService = schemaService; } + /** + * Sets the the CXF bus. + * + * @param serverBus the CXF bus + */ @Reference(cardinality = ReferenceCardinality.MANDATORY) public void setServerBus(Bus serverBus) { this.serverBus = serverBus; } + /** + * Sets the the REST authentication configuration. + * + * @param restAuthenticationConfig the REST authentication configuration + */ @Reference(cardinality = ReferenceCardinality.MANDATORY) public void setRestAuthenticationConfig(RestAuthenticationConfig restAuthenticationConfig) { this.restAuthenticationConfig = restAuthenticationConfig; } + /** + * Sets the the config sharing service. + * + * @param configSharingService the config sharing service + */ @Reference(cardinality = ReferenceCardinality.MANDATORY) public void setConfigSharingService(ConfigSharingService configSharingService) { this.configSharingService = configSharingService; } + /** + * Sets the the tenant service. + * + * @param tenantService the tenant service + */ @Reference(cardinality = ReferenceCardinality.MANDATORY) public void setTenantService(TenantService tenantService) { this.tenantService = tenantService; } + /** + * Sets the the security service. + * + * @param securityService the security service + */ @Reference(cardinality = ReferenceCardinality.MANDATORY) public void setSecurityService(SecurityService securityService) { this.securityService = securityService; } + /** + * Sets the the execution context manager. + * + * @param executionContextManager the execution context manager + */ @Reference(cardinality = ReferenceCardinality.MANDATORY) public void setExecutionContextManager(ExecutionContextManager executionContextManager) { this.executionContextManager = executionContextManager; } + /** + * Sets the the security filter. + * + * @param securityFilter the security filter + */ @Reference(cardinality = ReferenceCardinality.MANDATORY) public void setSecurityFilter(SecurityFilter securityFilter) { this.securityFilter = securityFilter; } + /** + * Registers an exception mapper with the JAX-RS server. + * + * @param exceptionMapper the exception mapper to add + */ @Reference(cardinality = ReferenceCardinality.MULTIPLE) public void addExceptionMapper(ExceptionMapper exceptionMapper) { this.exceptionMappers.add(exceptionMapper); @@ -139,12 +187,23 @@ public void addExceptionMapper(ExceptionMapper exceptionMapper) { refreshServer(); } + /** + * Removes an exception mapper from the JAX-RS server. + * + * @param exceptionMapper the exception mapper to remove + */ public void removeExceptionMapper(ExceptionMapper exceptionMapper) { this.exceptionMappers.remove(exceptionMapper); timeOfLastUpdate = System.currentTimeMillis(); refreshServer(); } + /** + * Activates the REST server and opens the JAX-RS service tracker. + * + * @param componentContext the OSGi component context + * @throws Exception if activation fails + */ @Activate public void activate(ComponentContext componentContext) throws Exception { this.bundleContext = componentContext.getBundleContext(); @@ -160,6 +219,11 @@ public void activate(ComponentContext componentContext) throws Exception { LOGGER.info("RestServer activated and service tracker opened"); } + /** + * Deactivates the REST server and releases tracked services. + * + * @throws Exception if deactivation fails + */ @Deactivate public void deactivate() throws Exception { LOGGER.info("RestServer deactivating..."); diff --git a/rest/src/main/java/org/apache/unomi/rest/server/RestServerBus.java b/rest/src/main/java/org/apache/unomi/rest/server/RestServerBus.java index ff87ba386..13a7c7f0a 100644 --- a/rest/src/main/java/org/apache/unomi/rest/server/RestServerBus.java +++ b/rest/src/main/java/org/apache/unomi/rest/server/RestServerBus.java @@ -26,6 +26,9 @@ */ @Component(service = Bus.class) public class RestServerBus extends ExtensionManagerBus implements Bus { + /** + * Creates the REST server CXF bus with logging and metrics features. + */ public RestServerBus() { this.getFeatures().add(new LoggingFeature()); this.getFeatures().add(new org.apache.cxf.metrics.MetricsFeature()); diff --git a/rest/src/main/java/org/apache/unomi/rest/server/provider/RetroCompatibilityParamConverterProvider.java b/rest/src/main/java/org/apache/unomi/rest/server/provider/RetroCompatibilityParamConverterProvider.java index fabd37121..d76e3efa0 100644 --- a/rest/src/main/java/org/apache/unomi/rest/server/provider/RetroCompatibilityParamConverterProvider.java +++ b/rest/src/main/java/org/apache/unomi/rest/server/provider/RetroCompatibilityParamConverterProvider.java @@ -44,6 +44,11 @@ public class RetroCompatibilityParamConverterProvider implements ParamConverterP private final ObjectMapper objectMapper; private final List> allowedConversionForTypes = new ArrayList<>(); + /** + * Creates the retro-compatibility parameter converter provider. + * + * @param objectMapper the Jackson object mapper + */ public RetroCompatibilityParamConverterProvider(ObjectMapper objectMapper) { this.objectMapper = objectMapper; diff --git a/rest/src/main/java/org/apache/unomi/rest/service/impl/LocalizationHelper.java b/rest/src/main/java/org/apache/unomi/rest/service/impl/LocalizationHelper.java index 01fd8ec7b..99a055988 100644 --- a/rest/src/main/java/org/apache/unomi/rest/service/impl/LocalizationHelper.java +++ b/rest/src/main/java/org/apache/unomi/rest/service/impl/LocalizationHelper.java @@ -47,6 +47,11 @@ public class LocalizationHelper { @Reference private ResourceBundleHelper resourceBundleHelper; + /** + * Activates the helper and stores the bundle context. + * + * @param componentContext the OSGi component context + */ @Activate public void activate(ComponentContext componentContext) { this.bundleContext = componentContext.getBundleContext(); @@ -192,10 +197,20 @@ public RESTValueType generateValueType(ValueType valueType, String language) { return result; } + /** + * Sets the OSGi bundle context. + * + * @param bundleContext the bundle context + */ public void setBundleContext(BundleContext bundleContext) { this.bundleContext = bundleContext; } + /** + * Sets the resource bundle helper. + * + * @param resourceBundleHelper the resource bundle helper + */ public void setResourceBundleHelper(ResourceBundleHelper resourceBundleHelper) { this.resourceBundleHelper = resourceBundleHelper; } diff --git a/rest/src/main/java/org/apache/unomi/rest/service/impl/ResourceBundleHelper.java b/rest/src/main/java/org/apache/unomi/rest/service/impl/ResourceBundleHelper.java index b68dd1f37..f5ac76370 100644 --- a/rest/src/main/java/org/apache/unomi/rest/service/impl/ResourceBundleHelper.java +++ b/rest/src/main/java/org/apache/unomi/rest/service/impl/ResourceBundleHelper.java @@ -30,6 +30,9 @@ import java.util.ResourceBundle; import java.util.regex.Pattern; +/** + * Helper for loading localized REST resource bundles from OSGi bundles. + */ @Component(service=ResourceBundleHelper.class) public class ResourceBundleHelper { @@ -39,6 +42,11 @@ public class ResourceBundleHelper { private BundleContext bundleContext; + /** + * Activates the helper and stores the bundle context. + * + * @param componentContext the OSGi component context + */ @Activate public void activate(ComponentContext componentContext) { this.bundleContext = componentContext.getBundleContext(); @@ -71,6 +79,13 @@ private Locale getLocale(String lang) { return Locale.forLanguageTag(lang); } + /** + * Returns the resource bundle for the given plugin type and language. + * + * @param object the plugin type + * @param language the requested language + * @return the matching resource bundle, or {@code null} + */ public ResourceBundle getResourceBundle(PluginType object, String language) { ResourceBundle resourceBundle = null; @@ -101,6 +116,13 @@ public ResourceBundle getResourceBundle(PluginType object, String language) { return resourceBundle; } + /** + * Returns a localized value from the resource bundle. + * + * @param bundle the resource bundle + * @param nameKey the message key + * @return the localized value, or the key when missing + */ public String getResourceBundleValue(ResourceBundle bundle, String nameKey) { try { if (bundle != null) { @@ -112,6 +134,11 @@ public String getResourceBundleValue(ResourceBundle bundle, String nameKey) { return "???" + nameKey + "???"; } + /** + * Sets the OSGi bundle context. + * + * @param bundleContext the bundle context + */ public void setBundleContext(BundleContext bundleContext) { this.bundleContext = bundleContext; } diff --git a/rest/src/main/java/org/apache/unomi/rest/service/impl/RestServiceUtilsImpl.java b/rest/src/main/java/org/apache/unomi/rest/service/impl/RestServiceUtilsImpl.java index efe3159ef..5a17da207 100644 --- a/rest/src/main/java/org/apache/unomi/rest/service/impl/RestServiceUtilsImpl.java +++ b/rest/src/main/java/org/apache/unomi/rest/service/impl/RestServiceUtilsImpl.java @@ -57,6 +57,9 @@ import java.util.Set; import java.util.UUID; +/** + * Default implementation of {@link org.apache.unomi.rest.service.RestServiceUtils}. + */ @Component(service = RestServiceUtils.class) public class RestServiceUtilsImpl implements RestServiceUtils { diff --git a/rest/src/main/java/org/apache/unomi/rest/tenants/TenantRequest.java b/rest/src/main/java/org/apache/unomi/rest/tenants/TenantRequest.java index 375548f7c..cddc93b4e 100644 --- a/rest/src/main/java/org/apache/unomi/rest/tenants/TenantRequest.java +++ b/rest/src/main/java/org/apache/unomi/rest/tenants/TenantRequest.java @@ -18,23 +18,48 @@ import java.util.Map; +/** + * REST request payload for creating or updating a {@link org.apache.unomi.api.tenants.Tenant}. + * Carries the desired tenant id and an open properties map that maps to + * tenant configuration fields accepted by {@link org.apache.unomi.api.tenants.TenantService}. + */ public class TenantRequest { private String requestedId; private Map properties; + /** + * Returns the requested tenant identifier. + * + * @return the requested tenant identifier + */ public String getRequestedId() { return requestedId; } + /** + * Sets the requested tenant identifier. + * + * @param requestedId the requested tenant identifier + */ public void setRequestedId(String requestedId) { this.requestedId = requestedId; } + /** + * Returns the tenant properties. + * + * @return the tenant properties + */ public Map getProperties() { return properties; } + /** + * Sets the tenant properties. + * + * @param properties the tenant properties + */ public void setProperties(Map properties) { this.properties = properties; } -} \ No newline at end of file +} \ No newline at end of file diff --git a/rest/src/main/java/org/apache/unomi/rest/validation/request/RequestValidatorInterceptor.java b/rest/src/main/java/org/apache/unomi/rest/validation/request/RequestValidatorInterceptor.java index ae19c1039..d6e9ebfbe 100644 --- a/rest/src/main/java/org/apache/unomi/rest/validation/request/RequestValidatorInterceptor.java +++ b/rest/src/main/java/org/apache/unomi/rest/validation/request/RequestValidatorInterceptor.java @@ -41,6 +41,11 @@ public class RequestValidatorInterceptor extends AbstractPhaseInterceptor + * Temporary duplicate of the WAB bundle class; the original will be removed once endpoints are fully forwarded. */ public class HttpUtils { private static final Logger LOGGER = LoggerFactory.getLogger(HttpUtils.class); @@ -61,6 +63,12 @@ public static String dumpRequestInfo(HttpServletRequest httpServletRequest) { return stringBuilder.toString(); } + /** + * Dumps basic request metadata. + * + * @param httpServletRequest the HTTP request + * @return the request metadata as text + */ public static String dumpBasicRequestInfo(HttpServletRequest httpServletRequest) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(httpServletRequest.getMethod()).append(" ").append(httpServletRequest.getRequestURI()); @@ -71,7 +79,12 @@ public static String dumpBasicRequestInfo(HttpServletRequest httpServletRequest) return stringBuilder.toString(); } - + /** + * Dumps request cookies. + * + * @param cookies the request cookies + * @return the cookie dump as text + */ public static String dumpRequestCookies(Cookie[] cookies) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("Cookies:\n"); @@ -85,6 +98,12 @@ public static String dumpRequestCookies(Cookie[] cookies) { return stringBuilder.toString(); } + /** + * Dumps request headers. + * + * @param httpServletRequest the HTTP request + * @return the header dump as text + */ public static String dumpRequestHeaders(HttpServletRequest httpServletRequest) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("Headers:\n"); @@ -97,12 +116,14 @@ public static String dumpRequestHeaders(HttpServletRequest httpServletRequest) { } /** - * Return the cookie string for the given profile - * We can't use the build in NewCookie jax-rs object as it does not support the SameSite value. + * Builds a {@code Set-Cookie} header value for the profile ID cookie. + *

+ * Uses a raw header string because JAX-RS {@code NewCookie} does not support {@code SameSite}. * - * @param profile to parse - * @param configSharingService shared config location. - * @return the cookie string to set in the header. + * @param profile the profile whose ID is stored in the cookie + * @param configSharingService shared configuration for cookie name, domain, and flags + * @param isSecure whether the cookie should include the {@code Secure} flag + * @return the cookie header value */ public static String getProfileCookieString(Profile profile, ConfigSharingService configSharingService, boolean isSecure) { final String profileIdCookieDomain = (String) configSharingService.getProperty("profileIdCookieDomain"); @@ -118,6 +139,15 @@ public static String getProfileCookieString(Profile profile, ConfigSharingServic (profileIdCookieHttpOnly ? "; HttpOnly" : ""); } + /** + * Filters event nodes to those that pass schema validation. + * + * @param eventsNode the event JSON array + * @param schemaService the schema service + * @param jsonParser the JSON parser + * @return the validated events + * @throws JsonProcessingException if event deserialization fails + */ public static List filterValidEvents(ArrayNode eventsNode, SchemaService schemaService, JsonParser jsonParser) throws JsonProcessingException { List filteredEvents = new ArrayList<>(); for (JsonNode event : eventsNode) { diff --git a/scripting/src/main/java/org/apache/unomi/scripting/ExpressionFilter.java b/scripting/src/main/java/org/apache/unomi/scripting/ExpressionFilter.java index 985ed17e6..e6ba08ee9 100644 --- a/scripting/src/main/java/org/apache/unomi/scripting/ExpressionFilter.java +++ b/scripting/src/main/java/org/apache/unomi/scripting/ExpressionFilter.java @@ -31,11 +31,23 @@ public class ExpressionFilter { private final Set allowedExpressionPatterns; private final Set forbiddenExpressionPatterns; + /** + * Creates an expression filter from allowed and forbidden pattern sets. + * + * @param allowedExpressionPatterns patterns that must match for acceptance, or {@code null} to skip allow checks + * @param forbiddenExpressionPatterns patterns that reject a match, or {@code null} to skip deny checks + */ public ExpressionFilter(Set allowedExpressionPatterns, Set forbiddenExpressionPatterns) { this.allowedExpressionPatterns = allowedExpressionPatterns; this.forbiddenExpressionPatterns = forbiddenExpressionPatterns; } + /** + * Applies allow/deny rules to an expression. + * + * @param expression the expression to validate + * @return the expression when accepted, or {@code null} when filtered out + */ public String filter(String expression) { if (forbiddenExpressionPatterns != null && expressionMatches(expression, forbiddenExpressionPatterns)) { LOGGER.warn("Expression filtered because forbidden. See debug log level for more information"); diff --git a/scripting/src/main/java/org/apache/unomi/scripting/MvelScriptExecutor.java b/scripting/src/main/java/org/apache/unomi/scripting/MvelScriptExecutor.java index 8777b0f75..cb706050f 100644 --- a/scripting/src/main/java/org/apache/unomi/scripting/MvelScriptExecutor.java +++ b/scripting/src/main/java/org/apache/unomi/scripting/MvelScriptExecutor.java @@ -35,6 +35,11 @@ public class MvelScriptExecutor implements ScriptExecutor { private SecureFilteringClassLoader secureFilteringClassLoader = new SecureFilteringClassLoader(getClass().getClassLoader()); private ExpressionFilterFactory expressionFilterFactory; + /** + * Sets the factory used to obtain expression filters per script language. + * + * @param expressionFilterFactory the expression filter factory + */ public void setExpressionFilterFactory(ExpressionFilterFactory expressionFilterFactory) { this.expressionFilterFactory = expressionFilterFactory; } diff --git a/scripting/src/main/java/org/apache/unomi/scripting/ScriptExecutor.java b/scripting/src/main/java/org/apache/unomi/scripting/ScriptExecutor.java index 1e8ace252..824250933 100644 --- a/scripting/src/main/java/org/apache/unomi/scripting/ScriptExecutor.java +++ b/scripting/src/main/java/org/apache/unomi/scripting/ScriptExecutor.java @@ -23,6 +23,13 @@ */ public interface ScriptExecutor { + /** + * Executes a script in the given context. + * + * @param script the script source to execute + * @param context variable bindings available to the script + * @return the script result, or {@code null} when execution is blocked or fails + */ Object execute(String script, Map context); } diff --git a/scripting/src/main/java/org/apache/unomi/scripting/internal/ExpressionFilterFactoryImpl.java b/scripting/src/main/java/org/apache/unomi/scripting/internal/ExpressionFilterFactoryImpl.java index cdfd6f4d9..c7c75a175 100644 --- a/scripting/src/main/java/org/apache/unomi/scripting/internal/ExpressionFilterFactoryImpl.java +++ b/scripting/src/main/java/org/apache/unomi/scripting/internal/ExpressionFilterFactoryImpl.java @@ -34,6 +34,9 @@ import java.util.*; import java.util.regex.Pattern; +/** + * OSGi-backed factory that loads allowed and forbidden expression patterns for script execution. + */ public class ExpressionFilterFactoryImpl implements ExpressionFilterFactory,BundleListener { private static final Logger LOGGER = LoggerFactory.getLogger(ExpressionFilterFactoryImpl.class.getName()); @@ -48,13 +51,24 @@ public class ExpressionFilterFactoryImpl implements ExpressionFilterFactory,Bund private boolean expressionFiltersActivated = Boolean.parseBoolean(System.getProperty("org.apache.unomi.scripting.filter.activated", "true")); + /** + * Sets the OSGi bundle context used to discover expression pattern resources. + * + * @param bundleContext the bundle context + */ public void setBundleContext(BundleContext bundleContext) { this.bundleContext = bundleContext; } + /** + * Creates the factory instance. + */ public ExpressionFilterFactoryImpl() { } + /** + * Loads configured expression patterns and registers bundle listeners. + */ public void init() { String initialFilterCollections = System.getProperty("org.apache.unomi.scripting.filter.collections", "mvel"); String[] initialFilterCollectionParts = initialFilterCollections.split(","); @@ -94,12 +108,20 @@ private Set loadPatternsFromConfig(String propertyKey) { return null; } + /** + * Unregisters bundle listeners and releases OSGi resources. + */ public void destroy() { if (bundleContext != null) { bundleContext.removeBundleListener(this); } } + /** + * Reacts to bundle start and stop events to refresh expression patterns. + * + * @param event the bundle lifecycle event + */ public void bundleChanged(BundleEvent event) { switch (event.getType()) { case BundleEvent.STARTED: 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 eea7ea1ca..f1a7ea87c 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 @@ -109,6 +109,11 @@ private ReadWriteLock typeRefreshLock(String itemType) { protected final Map scheduledRefreshTasks = new ConcurrentHashMap<>(); // Each service defines its supported types + /** + * Returns the cache type configurations supported by this service. + * + * @return supported cacheable type configurations + */ protected abstract Set> getTypeConfigs(); /** @@ -216,12 +221,18 @@ public void preDestroy() { logger.debug("{} service shutdown.", getClass().getSimpleName()); } + /** + * Registers cache types defined by {@link #getTypeConfigs()}. + */ protected void initializeCaches() { for (CacheableTypeConfig config : getTypeConfigs()) { cacheService.registerType(config); } } + /** + * Schedules periodic refresh tasks for cache types that require them. + */ protected void initializeTimers() { // Initialize refresh timers for types that need it for (CacheableTypeConfig config : getTypeConfigs()) { @@ -231,6 +242,11 @@ protected void initializeTimers() { } } + /** + * Schedules a periodic refresh task for the given cache type. + * + * @param config the cache type configuration + */ protected void scheduleTypeRefresh(CacheableTypeConfig config) { String taskName = "cache-refresh-" + config.getType().getSimpleName(); // Avoid rescheduling if a task with the same name already exists @@ -266,6 +282,9 @@ protected void scheduleTypeRefresh(CacheableTypeConfig config) { logger.debug("Scheduled cache refresh for type: {}", config.getType().getSimpleName()); } + /** + * Cancels all scheduled cache refresh tasks. + */ protected void shutdownTimers() { logger.info("Shutting down cache refresh timers..."); for (Map.Entry entry : scheduledRefreshTasks.entrySet()) { @@ -283,6 +302,12 @@ protected void shutdownTimers() { scheduledRefreshTasks.clear(); } + /** + * Reloads cached items for a type from persistence. + * + * @param the cached item type + * @param config the cache type configuration + */ protected void refreshTypeCache(CacheableTypeConfig config) { if (!config.isRequiresRefresh()) { return; @@ -409,6 +434,14 @@ private void refreshTypeCacheLocked(CacheableTypeConfig } } + /** + * Loads items for a tenant from persistence, including inherited system-tenant items when configured. + * + * @param the cached item type + * @param tenantId the tenant identifier + * @param config the cache type configuration + * @return items loaded for the tenant + */ @SuppressWarnings("unchecked") protected List loadItemsForTenant(String tenantId, CacheableTypeConfig config) { List items = new ArrayList<>(); @@ -484,6 +517,14 @@ protected List loadItemsForTenant(String tenantId, C return items; } + /** + * Applies optional post-processing and stores items in the tenant cache. + * + * @param the cached item type + * @param tenantId the tenant identifier + * @param items the items to cache + * @param config the cache type configuration + */ protected void processAndCacheItems(String tenantId, List items, CacheableTypeConfig config) { for (T item : items) { // Apply post-processor if defined @@ -496,6 +537,11 @@ protected void processAndCacheItems(String tenantId, Li } } + /** + * Returns all tenant identifiers, including the system tenant. + * + * @return tenant identifiers known to the service + */ protected Set getTenants() { Set tenants = new HashSet<>(); for (Tenant tenant : tenantService.getAllTenants()) { @@ -505,6 +551,11 @@ protected Set getTenants() { return tenants; } + /** + * Loads predefined JSON items from bundle resources for all configured types. + * + * @param bundleContext the bundle context used to discover predefined resources + */ protected void loadPredefinedItems(BundleContext bundleContext) { if (bundleContext == null) return; @@ -535,6 +586,13 @@ protected void addPluginContribution(long bundleId, Object item) { pluginContributions.computeIfAbsent(bundleId, k -> new CopyOnWriteArrayList<>()).add(item); } + /** + * Loads predefined JSON items for one cache type from bundle resources. + * + * @param the cached item type + * @param bundleContext the bundle context used to discover predefined resources + * @param config the cache type configuration + */ @SuppressWarnings("unchecked") protected void loadPredefinedItemsForType(BundleContext bundleContext, CacheableTypeConfig config) { // Skip if this type doesn't have predefined items @@ -765,6 +823,7 @@ public Map> getTypesByPlugin() { * * @param the type of items to retrieve * @param itemClass the class of the items to retrieve + * @param withInherited {@code true} to include inherited system-tenant items * @return a collection of all items of the specified type */ protected Collection getAllItems(Class itemClass, boolean withInherited) { @@ -932,6 +991,10 @@ protected void removeItem(String id, Class it * The read lock is exclusive with refreshTypeCache()'s write lock, which prevents a * concurrent cache refresh from re-populating subclass state after removal. * Default implementation is a no-op. + * + * @param id the removed item identifier + * @param itemType the removed item type + * @param tenantId the tenant that owned the removed item */ protected void onItemRemoved(String id, String itemType, String tenantId) { } diff --git a/services/src/main/java/org/apache/unomi/services/impl/scheduler/PersistenceSchedulerProvider.java b/services/src/main/java/org/apache/unomi/services/impl/scheduler/PersistenceSchedulerProvider.java index 1f7f1753a..bde2905ac 100644 --- a/services/src/main/java/org/apache/unomi/services/impl/scheduler/PersistenceSchedulerProvider.java +++ b/services/src/main/java/org/apache/unomi/services/impl/scheduler/PersistenceSchedulerProvider.java @@ -28,6 +28,9 @@ import java.util.*; +/** + * Persistence-backed scheduler provider for durable, cluster-aware tasks. + */ public class PersistenceSchedulerProvider implements SchedulerProvider { private static final Logger LOGGER = LoggerFactory.getLogger(PersistenceSchedulerProvider.class.getName()); @@ -55,38 +58,79 @@ public class PersistenceSchedulerProvider implements SchedulerProvider { private TaskLockManager lockManager; private ClusterService clusterService; + /** + * Sets the persistence service via Blueprint dependency injection. + * + * @param persistenceService the persistence service + */ public void setPersistenceService(PersistenceService persistenceService) { this.persistenceService = persistenceService; } + /** + * Sets whether this node executes scheduled tasks. + * + * @param executorNode true if this node runs tasks + */ public void setExecutorNode(boolean executorNode) { this.executorNode = executorNode; } + /** + * Sets the cluster node ID for this provider. + * + * @param nodeId the node ID + */ public void setNodeId(String nodeId) { this.nodeId = nodeId; } + /** + * Sets the TTL in days for purging completed tasks. + * + * @param completedTaskTtlDays retention period in days + */ public void setCompletedTaskTtlDays(long completedTaskTtlDays) { this.completedTaskTtlDays = completedTaskTtlDays; } + /** + * Sets the task lock manager. + * + * @param lockManager the lock manager + */ public void setLockManager(TaskLockManager lockManager) { this.lockManager = lockManager; } + /** + * Sets the cluster service for active-node discovery. + * + * @param clusterService the cluster service + */ public void setClusterService(ClusterService clusterService) { this.clusterService = clusterService; } + /** + * Clears the cluster service reference on unbind. + * + * @param clusterService the cluster service being unbound + */ public void unsetClusterService(ClusterService clusterService) { this.clusterService = null; } + /** + * Blueprint post-construct hook. + */ public void postConstruct() { } + /** + * Blueprint pre-destroy hook; releases locks held by this node. + */ public void preDestroy() { // Check if persistence service is still available before trying to use it if (persistenceService == null) { diff --git a/services/src/main/java/org/apache/unomi/services/impl/scheduler/SchedulerProvider.java b/services/src/main/java/org/apache/unomi/services/impl/scheduler/SchedulerProvider.java index 8cb1401d9..d2bc83bb6 100644 --- a/services/src/main/java/org/apache/unomi/services/impl/scheduler/SchedulerProvider.java +++ b/services/src/main/java/org/apache/unomi/services/impl/scheduler/SchedulerProvider.java @@ -42,30 +42,82 @@ public interface SchedulerProvider { ConditionType PROPERTY_CONDITION_TYPE = new ConditionType(); ConditionType BOOLEAN_CONDITION_TYPE = new ConditionType(); + /** + * Finds tasks currently locked by the given owner node. + * + * @param owner the lock owner node ID + * @return tasks locked by the owner + */ List findTasksByLockOwner(String owner); + /** + * Finds enabled tasks in SCHEDULED or WAITING status. + * + * @return matching tasks + */ List findEnabledScheduledOrWaitingTasks(); + /** + * Finds tasks of the given type and status. + * + * @param taskType the task type + * @param status the task status + * @return matching tasks + */ List findTasksByTypeAndStatus(String taskType, ScheduledTask.TaskStatus status); + /** + * Loads a task by ID. + * + * @param taskId the task ID + * @return the task, or null if not found + */ ScheduledTask getTask(String taskId); + /** + * Returns all tasks from this provider. + * + * @return all tasks + */ List getAllTasks(); + /** + * Returns a paginated list of tasks with the given status. + * + * @param status the task status filter + * @param offset pagination offset + * @param size page size (-1 for all) + * @param sortBy sort field + * @return paginated task list + */ PartialList getTasksByStatus(ScheduledTask.TaskStatus status, int offset, int size, String sortBy); + /** + * Returns a paginated list of tasks of the given type. + * + * @param taskType the task type filter + * @param offset pagination offset + * @param size page size (-1 for all) + * @param sortBy sort field + * @return paginated task list + */ PartialList getTasksByType(String taskType, int offset, int size, String sortBy); + /** + * Removes completed tasks older than the configured TTL. + */ void purgeOldTasks(); /** * Permanently removes a task record from storage. + * * @param taskId the task ID to remove */ void deleteTask(String taskId); /** * Saves a task to the persistence service if it's persistent. + * * @param task The task to save * @return true if the task was successfully saved, false otherwise */ @@ -92,12 +144,17 @@ public interface SchedulerProvider { void refreshTasks(); /** - * Finds tasks by status + * Finds tasks with the given status. + * + * @param status the task status filter + * @return matching tasks */ List findTasksByStatus(ScheduledTask.TaskStatus status); /** - * Finds tasks with locks + * Finds tasks that currently hold a lock. + * + * @return locked tasks */ List findLockedTasks(); } 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 646eb3561..dc305c0b4 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 @@ -112,6 +112,8 @@ public class SchedulerServiceImpl implements SchedulerService { /** * Finds all persistent tasks that are currently locked (i.e., have a lock owner and are not expired). * This is used by the recovery manager to detect tasks that may need to be recovered if their lock has expired. + * + * @return locked persistent tasks */ public List findLockedTasks() { List lockedTasks = new ArrayList<>(); @@ -230,6 +232,7 @@ private enum TaskTransition { /** * Checks if a state transition is valid + * * @param from Current task state * @param to Target task state * @return true if transition is valid @@ -243,6 +246,7 @@ public static boolean isValidTransition(TaskStatus from, TaskStatus to) { /** * Checks if all required services are initialized and available + * * @return true if services are ready, false otherwise */ private boolean areServicesReady() { @@ -253,6 +257,7 @@ private boolean areServicesReady() { /** * Checks if all required services are initialized and available, including persistence provider if required + * * @param requirePersistenceProvider Whether the operation requires persistence provider to be available * @return true if services are ready, false otherwise */ @@ -271,6 +276,7 @@ private boolean areServicesReady(boolean requirePersistenceProvider) { /** * Queues an operation to be executed once services are available + * * @param type The type of operation * @param description Human-readable description of the operation * @param parameters The parameters for the operation @@ -281,6 +287,7 @@ private void queuePendingOperation(OperationType type, String description, Objec /** * Queues an operation to be executed once services are available + * * @param type The type of operation * @param description Human-readable description of the operation * @param requirePersistenceProvider Whether the operation requires persistence provider to be available @@ -383,6 +390,7 @@ private void processPendingOperations() { /** * Determines if an operation type requires the persistence provider to be available + * * @param operation The pending operation * @return true if the operation requires persistence provider, false otherwise */ @@ -409,6 +417,7 @@ private boolean requiresPersistenceProvider(PendingOperation operation) { /** * Executes a specific pending operation + * * @param operation The operation to execute */ private void executePendingOperation(PendingOperation operation) { @@ -459,6 +468,7 @@ private void executePendingOperation(PendingOperation operation) { /** * Updates task state with validation and persistence + * * @param task The task to update * @param newStatus The new status to set * @param error Optional error message for failed states @@ -552,46 +562,98 @@ public Object get(long timeout, TimeUnit unit) { } }; + /** + * Creates the scheduler service for Blueprint dependency injection. + */ public SchedulerServiceImpl() { } + /** + * Sets the OSGi bundle context. + * + * @param bundleContext the bundle context + */ public void setBundleContext(BundleContext bundleContext) { this.bundleContext = bundleContext; } - // Setter methods for Blueprint dependency injection + /** + * Sets the task state manager. + * + * @param stateManager the state manager + */ public void setStateManager(TaskStateManager stateManager) { this.stateManager = stateManager; } + /** + * Sets the task lock manager. + * + * @param lockManager the lock manager + */ public void setLockManager(TaskLockManager lockManager) { this.lockManager = lockManager; } + /** + * Sets the task execution manager. + * + * @param executionManager the execution manager + */ public void setExecutionManager(TaskExecutionManager executionManager) { this.executionManager = executionManager; } + /** + * Sets the task recovery manager. + * + * @param recoveryManager the recovery manager + */ public void setRecoveryManager(TaskRecoveryManager recoveryManager) { this.recoveryManager = recoveryManager; } + /** + * Sets the task metrics manager. + * + * @param metricsManager the metrics manager + */ public void setMetricsManager(TaskMetricsManager metricsManager) { this.metricsManager = metricsManager; } + /** + * Sets the task history manager. + * + * @param historyManager the history manager + */ public void setHistoryManager(TaskHistoryManager historyManager) { this.historyManager = historyManager; } + /** + * Sets the task validation manager. + * + * @param validationManager the validation manager + */ public void setValidationManager(TaskValidationManager validationManager) { this.validationManager = validationManager; } + /** + * Sets the task executor registry. + * + * @param executorRegistry the executor registry + */ public void setExecutorRegistry(TaskExecutorRegistry executorRegistry) { this.executorRegistry = executorRegistry; } + /** + * Binds the persistence-backed scheduler provider. + * + * @param persistenceProvider the persistence provider + */ public void setPersistenceProvider(SchedulerProvider persistenceProvider) { this.persistenceProvider = persistenceProvider; LOGGER.debug("PersistenceSchedulerProvider bound to SchedulerService"); @@ -608,6 +670,7 @@ public void setPersistenceProvider(SchedulerProvider persistenceProvider) { /** * Checks if all remaining operations in the queue require the persistence provider + * * @return true if all remaining operations require persistence, false otherwise */ private boolean checkIfAllRemainingOperationsRequirePersistence() { @@ -677,6 +740,11 @@ private void clearExpiredOperations() { } } + /** + * Unbinds the persistence-backed scheduler provider. + * + * @param persistenceProvider the persistence provider being unbound + */ public void unsetPersistenceProvider(SchedulerProvider persistenceProvider) { this.persistenceProvider = null; LOGGER.debug("PersistenceSchedulerProvider unbound from SchedulerService"); @@ -692,6 +760,9 @@ public void purgeOldTasks() { } } + /** + * Blueprint post-construct hook; starts task checking on executor nodes. + */ public void postConstruct() { if (bundleContext == null) { LOGGER.error("BundleContext is null, cannot initialize service trackers"); @@ -734,6 +805,9 @@ public void postConstruct() { processPendingOperations(); } + /** + * Blueprint pre-destroy hook; shuts down task execution and releases locks. + */ public void preDestroy() { /** * Explicit shutdown sequence to handle the Aries Blueprint bug. @@ -852,6 +926,7 @@ public void preDestroy() { /** * Checks if the scheduler is shutting down. * This method is used by TaskExecutionManager to skip task execution during shutdown. + * * @return true if the scheduler is shutting down, false otherwise */ public boolean isShutdownNow() { @@ -1105,6 +1180,7 @@ public void scheduleTask(ScheduledTask task) { /** * Internal method to schedule a task - called when services are ready + * * @param task The task to schedule */ private void scheduleTaskInternal(ScheduledTask task) { @@ -1164,6 +1240,7 @@ public void deleteTask(String taskId) { /** * Internal method to cancel a task - called when services are ready + * * @param taskId The task ID to cancel */ private void cancelTaskInternal(String taskId) { @@ -1290,6 +1367,11 @@ public boolean isExecutorNode() { return executorNode; } + /** + * Sets the cluster node ID. + * + * @param nodeId the node ID + */ public void setNodeId(String nodeId) { this.nodeId = nodeId; } @@ -1399,26 +1481,58 @@ public PartialList getTasksByType(String taskType, int offset, in totalSize <= offset + (size == -1 ? totalSize : size) ? PartialList.Relation.EQUAL : PartialList.Relation.GREATER_THAN_OR_EQUAL_TO); } + /** + * Sets the scheduler thread pool size. + * + * @param threadPoolSize the thread pool size + */ public void setThreadPoolSize(int threadPoolSize) { this.threadPoolSize = threadPoolSize; } + /** + * Sets whether this node executes scheduled tasks. + * + * @param executorNode true if this node runs tasks + */ public void setExecutorNode(boolean executorNode) { this.executorNode = executorNode; } + /** + * Sets the distributed lock timeout in milliseconds. + * + * @param lockTimeout lock expiry timeout + */ public void setLockTimeout(long lockTimeout) { this.lockTimeout = lockTimeout; } + /** + * Sets the TTL in days for purging completed tasks. + * + * @param completedTaskTtlDays retention period in days + */ public void setCompletedTaskTtlDays(long completedTaskTtlDays) { this.completedTaskTtlDays = completedTaskTtlDays; } + /** + * Sets whether the purge task is enabled. + * + * @param purgeTaskEnabled true to enable periodic purge + */ public void setPurgeTaskEnabled(boolean purgeTaskEnabled) { this.purgeTaskEnabled = purgeTaskEnabled; } + /** + * Returns seconds until the next run at the given UTC hour. + * + * @param hourInUtc target hour in UTC (0-23) + * @param now current time + * @return seconds until the next run + */ public static long getTimeDiffInSeconds(int hourInUtc, ZonedDateTime now) { ZonedDateTime nextRun = now.withHour(hourInUtc).withMinute(0).withSecond(0); if(now.compareTo(nextRun) > 0) { @@ -1450,6 +1564,7 @@ public void retryTask(String taskId, boolean resetFailureCount) { /** * Internal method to retry a task - called when services are ready + * * @param taskId The task ID to retry * @param resetFailureCount Whether to reset the failure count */ @@ -1478,6 +1593,7 @@ public void resumeTask(String taskId) { /** * Internal method to resume a task - called when services are ready + * * @param taskId The task ID to resume */ private void resumeTaskInternal(String taskId) { @@ -1572,7 +1688,10 @@ public void execute(ScheduledTask task, TaskStatusCallback callback) { } /** - * Builder class to simplify task creation with fluent API + * Creates a fluent builder for a new task. + * + * @param taskType the task type + * @return a task builder */ public TaskBuilder newTask(String taskType) { return new TaskBuilder(this, taskType); @@ -1584,6 +1703,7 @@ private boolean updateTaskInPersistence(ScheduledTask task) { /** * Saves a task to the persistence service if it's persistent. + * * @param task The task to save * @return true if the task was successfully saved, false otherwise */ @@ -1764,6 +1884,7 @@ private int compareStrings(String str1, String str2) { /** * Gets the number of pending operations waiting to be processed + * * @return The number of pending operations */ public int getPendingOperationsCount() { @@ -1772,6 +1893,7 @@ public int getPendingOperationsCount() { /** * Gets a list of pending operations for debugging purposes + * * @return List of pending operation descriptions */ public List getPendingOperationsList() { @@ -1888,10 +2010,18 @@ public void simulateCrash() { } } + /** + * Returns the task lock manager. + * + * @return the lock manager + */ public TaskLockManager getLockManager() { return lockManager; } + /** + * Fluent builder for creating scheduled tasks. + */ public static class TaskBuilder implements SchedulerService.TaskBuilder { private final SchedulerServiceImpl schedulerService; private final String taskType; diff --git a/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskExecutionManager.java b/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskExecutionManager.java index 206e2eb00..b0155ad8b 100644 --- a/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskExecutionManager.java +++ b/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskExecutionManager.java @@ -49,40 +49,82 @@ public class TaskExecutionManager { private TaskExecutorRegistry executorRegistry; private int threadPoolSize = MIN_THREAD_POOL_SIZE; + /** + * Creates the execution manager. + */ public TaskExecutionManager() { this.scheduledTasks = new ConcurrentHashMap<>(); this.executingTasksByType = new ConcurrentHashMap<>(); } - // Setter methods for Blueprint dependency injection + /** + * Sets the cluster node ID. + * + * @param nodeId the node ID + */ public void setNodeId(String nodeId) { this.nodeId = nodeId; } + /** + * Sets the scheduler thread pool size. + * + * @param threadPoolSize the thread pool size + */ public void setThreadPoolSize(int threadPoolSize) { this.threadPoolSize = Math.max(MIN_THREAD_POOL_SIZE, threadPoolSize); } + /** + * Sets the task state manager. + * + * @param stateManager the state manager + */ public void setStateManager(TaskStateManager stateManager) { this.stateManager = stateManager; } + /** + * Sets the task lock manager. + * + * @param lockManager the lock manager + */ public void setLockManager(TaskLockManager lockManager) { this.lockManager = lockManager; } + /** + * Sets the task metrics manager. + * + * @param metricsManager the metrics manager + */ public void setMetricsManager(TaskMetricsManager metricsManager) { this.metricsManager = metricsManager; } + /** + * Sets the task history manager. + * + * @param historyManager the history manager + */ public void setHistoryManager(TaskHistoryManager historyManager) { this.historyManager = historyManager; } + /** + * Sets the task executor registry. + * + * @param executorRegistry the executor registry + */ public void setExecutorRegistry(TaskExecutorRegistry executorRegistry) { this.executorRegistry = executorRegistry; } + /** + * Sets the scheduler service reference. + * + * @param schedulerService the scheduler service + */ public void setSchedulerService(SchedulerServiceImpl schedulerService) { this.schedulerService = schedulerService; } @@ -105,7 +147,9 @@ public void initialize() { } /** - * Starts the task checking service if this is an executor node + * Starts the task checking service if this is an executor node. + * + * @param taskChecker runnable that polls for due tasks */ public void startTaskChecker(Runnable taskChecker) { if (running.compareAndSet(false, true)) { @@ -131,7 +175,10 @@ public void stopTaskChecker() { } /** - * Schedules a task for execution based on its configuration + * Schedules a task for execution based on its configuration. + * + * @param task the task to schedule + * @param taskRunner runnable invoked when the task is due */ public void scheduleTask(ScheduledTask task, Runnable taskRunner) { // Calculate initial execution time if not set @@ -159,6 +206,9 @@ public void scheduleTask(ScheduledTask task, Runnable taskRunner) { /** * Executes a task immediately with the specified executor. * This method should only be called when a task is ready to execute. + * + * @param task the task to execute + * @param executor the task executor implementation */ public void executeTask(ScheduledTask task, TaskExecutor executor) { try { @@ -205,7 +255,10 @@ public void executeTask(ScheduledTask task, TaskExecutor executor) { } /** - * Prepares a task for execution by validating state and acquiring lock if needed + * Prepares a task for execution by validating state and acquiring lock if needed. + * + * @param task the task to prepare + * @return true if the task is ready to run */ public boolean prepareForExecution(ScheduledTask task) { if (!task.isEnabled()) { @@ -497,7 +550,9 @@ private void updateTaskMetrics(ScheduledTask task, long startTime) { } /** - * Cancels a running task + * Cancels a running task. + * + * @param taskId the task ID to cancel */ public void cancelTask(String taskId) { ScheduledFuture future = scheduledTasks.remove(taskId); @@ -536,6 +591,11 @@ public void shutdown() { } } + /** + * Returns the internal scheduled executor service. + * + * @return the scheduler executor + */ public ScheduledExecutorService getScheduler() { return scheduler; } diff --git a/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskHistoryManager.java b/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskHistoryManager.java index ec917f07b..1425fc965 100644 --- a/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskHistoryManager.java +++ b/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskHistoryManager.java @@ -33,21 +33,36 @@ public class TaskHistoryManager { private String nodeId; private TaskMetricsManager metricsManager; + /** + * Creates the manager for Blueprint dependency injection. + */ public TaskHistoryManager() { // Parameterless constructor for Blueprint dependency injection } - // Setter methods for Blueprint dependency injection + /** + * Sets the cluster node ID. + * + * @param nodeId the node ID + */ public void setNodeId(String nodeId) { this.nodeId = nodeId; } + /** + * Sets the task metrics manager. + * + * @param metricsManager the metrics manager + */ public void setMetricsManager(TaskMetricsManager metricsManager) { this.metricsManager = metricsManager; } /** - * Records a successful task execution + * Records a successful task execution. + * + * @param task the completed task + * @param executionTime execution duration in milliseconds */ public void recordSuccess(ScheduledTask task, long executionTime) { Map entry = new HashMap<>(); @@ -55,14 +70,17 @@ public void recordSuccess(ScheduledTask task, long executionTime) { entry.put("status", "SUCCESS"); entry.put("nodeId", nodeId); entry.put("executionTime", executionTime); - + addToHistory(task, entry); metricsManager.updateMetric(TaskMetricsManager.METRIC_TASKS_COMPLETED); metricsManager.updateMetric(TaskMetricsManager.METRIC_TASKS_EXECUTION_TIME, executionTime); } /** - * Records a failed task execution + * Records a failed task execution. + * + * @param task the failed task + * @param error the error message */ public void recordFailure(ScheduledTask task, String error) { Map entry = new HashMap<>(); @@ -70,53 +88,67 @@ public void recordFailure(ScheduledTask task, String error) { entry.put("status", "FAILED"); entry.put("nodeId", nodeId); entry.put("error", error); - + addToHistory(task, entry); metricsManager.updateMetric(TaskMetricsManager.METRIC_TASKS_FAILED); } /** - * Records a task crash + * Records a task crash. + * + * @param task the crashed task */ public void recordCrash(ScheduledTask task) { Map entry = new HashMap<>(); entry.put("timestamp", new Date()); entry.put("status", "CRASHED"); entry.put("nodeId", nodeId); - + addToHistory(task, entry); metricsManager.updateMetric(TaskMetricsManager.METRIC_TASKS_CRASHED); } /** - * Records task cancellation + * Records task cancellation. + * + * @param task the cancelled task */ public void recordCancellation(ScheduledTask task) { Map entry = new HashMap<>(); entry.put("timestamp", new Date()); entry.put("status", "CANCELLED"); entry.put("nodeId", nodeId); - + addToHistory(task, entry); metricsManager.updateMetric(TaskMetricsManager.METRIC_TASKS_CANCELLED); } + /** + * Records task resumption after a crash. + * + * @param task the resumed task + */ public void recordResume(ScheduledTask task) { Map entry = new HashMap<>(); entry.put("timestamp", new Date()); entry.put("status", "RESUMED"); entry.put("nodeId", nodeId); - + addToHistory(task, entry); metricsManager.updateMetric(TaskMetricsManager.METRIC_TASKS_RESUMED); } + /** + * Records a task retry attempt. + * + * @param task the retried task + */ public void recordRetry(ScheduledTask task) { Map entry = new HashMap<>(); entry.put("timestamp", new Date()); entry.put("status", "RETRIED"); entry.put("nodeId", nodeId); - + addToHistory(task, entry); metricsManager.updateMetric(TaskMetricsManager.METRIC_TASKS_RETRIED); } @@ -152,7 +184,10 @@ private void addToHistory(ScheduledTask task, Map entry) { } /** - * Gets execution history for a task + * Returns execution history entries stored on the task. + * + * @param task the task + * @return execution history entries, or an empty list when none exist */ public List> getExecutionHistory(ScheduledTask task) { Map details = task.getStatusDetails(); @@ -164,4 +199,4 @@ public List> getExecutionHistory(ScheduledTask task) { List> history = (List>) details.get("executionHistory"); return history != null ? history : Collections.emptyList(); } -} \ No newline at end of file +} \ No newline at end of file diff --git a/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskLockManager.java b/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskLockManager.java index 43dc8ec05..0605ddb0f 100644 --- a/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskLockManager.java +++ b/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskLockManager.java @@ -25,7 +25,7 @@ /** * Manages task locks to coordinate execution in a cluster environment. - * This class ensures that tasks which don't allow parallel execution + * Ensures that tasks which do not allow parallel execution * only run on a single node at a time. * *

Distributed Locking Strategy:

@@ -75,23 +75,45 @@ public class TaskLockManager { private TaskMetricsManager metricsManager; private SchedulerServiceImpl schedulerService; + /** + * Creates the manager for Blueprint dependency injection. + */ public TaskLockManager() { // Parameterless constructor for Blueprint dependency injection } - // Setter methods for Blueprint dependency injection + /** + * Sets the cluster node ID. + * + * @param nodeId the node ID + */ public void setNodeId(String nodeId) { this.nodeId = nodeId; } + /** + * Sets the lock timeout in milliseconds. + * + * @param lockTimeout lock expiry timeout + */ public void setLockTimeout(long lockTimeout) { this.lockTimeout = lockTimeout; } + /** + * Sets the task metrics manager. + * + * @param metricsManager the metrics manager + */ public void setMetricsManager(TaskMetricsManager metricsManager) { this.metricsManager = metricsManager; } + /** + * Sets the scheduler service reference. + * + * @param schedulerService the scheduler service + */ public void setSchedulerService(SchedulerServiceImpl schedulerService) { this.schedulerService = schedulerService; } diff --git a/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskRecoveryManager.java b/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskRecoveryManager.java index 56c771317..7d6da402e 100644 --- a/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskRecoveryManager.java +++ b/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskRecoveryManager.java @@ -43,41 +43,78 @@ public class TaskRecoveryManager { private SchedulerServiceImpl schedulerService; private volatile boolean shutdownNow = false; + /** + * Creates the manager for Blueprint dependency injection. + */ public TaskRecoveryManager() { // Parameterless constructor for Blueprint dependency injection } - // Setter methods for Blueprint dependency injection + /** + * Sets the cluster node ID. + * + * @param nodeId the node ID + */ public void setNodeId(String nodeId) { this.nodeId = nodeId; } + /** + * Sets the task state manager. + * + * @param stateManager the state manager + */ public void setStateManager(TaskStateManager stateManager) { this.stateManager = stateManager; } + /** + * Sets the task lock manager. + * + * @param lockManager the lock manager + */ public void setLockManager(TaskLockManager lockManager) { this.lockManager = lockManager; } + /** + * Sets the task metrics manager. + * + * @param metricsManager the metrics manager + */ public void setMetricsManager(TaskMetricsManager metricsManager) { this.metricsManager = metricsManager; } + /** + * Sets the task execution manager. + * + * @param executionManager the execution manager + */ public void setExecutionManager(TaskExecutionManager executionManager) { this.executionManager = executionManager; } + /** + * Sets the task executor registry. + * + * @param executorRegistry the executor registry + */ public void setExecutorRegistry(TaskExecutorRegistry executorRegistry) { this.executorRegistry = executorRegistry; } + /** + * Sets the scheduler service reference. + * + * @param schedulerService the scheduler service + */ public void setSchedulerService(SchedulerServiceImpl schedulerService) { this.schedulerService = schedulerService; } /** - * Set the shutdown flag to prevent operations during shutdown + * Marks the manager as shutting down so recovery work is skipped. */ public void prepareForShutdown() { this.shutdownNow = true; @@ -85,12 +122,10 @@ public void prepareForShutdown() { } /** - * Recovers tasks that crashed due to node failure or unexpected termination - * Process: - * 1. Identify tasks with expired locks - * 2. Release locks and update states - * 3. Attempt to resume tasks with checkpoint data - * 4. Reschedule tasks that can't be resumed + * Recovers crashed and stale locked tasks after node failure. + *

+ * Running tasks with expired locks are marked crashed, then resumed or restarted. + * Expired locks on non-running tasks are released and eligible tasks are rescheduled. */ public void recoverCrashedTasks() { if (shutdownNow) { @@ -107,7 +142,7 @@ public void recoverCrashedTasks() { } /** - * Recovers tasks that are marked as running but have expired locks + * Recovers running tasks whose locks have expired. */ private void recoverRunningTasks() { if (shutdownNow) return; @@ -125,7 +160,9 @@ private void recoverRunningTasks() { } /** - * Recovers a single crashed task + * Recovers one crashed task by marking it crashed, recording history, and resuming or restarting it. + * + * @param task the task to recover */ private void recoverCrashedTask(ScheduledTask task) { // Skip cancelled tasks - they should not be recovered @@ -169,7 +206,10 @@ private void recoverCrashedTask(ScheduledTask task) { } /** - * Records a task crash in its execution history + * Appends a crash entry to the task execution history. + * + * @param task the crashed task + * @param previousOwner the node that previously held the lock */ private void recordCrash(ScheduledTask task, String previousOwner) { Map crash = new HashMap<>(); @@ -198,7 +238,10 @@ private void recordCrash(ScheduledTask task, String previousOwner) { } /** - * Attempts to resume a crashed task + * Reschedules and executes a crashed task that supports resumption. + * + * @param task the task to resume + * @param executor the executor for the task type */ private void attemptTaskResumption(ScheduledTask task, TaskExecutor executor) { LOGGER.info("Node {} resuming crashed task {} : {}", nodeId, task.getTaskType(), task.getItemId()); @@ -210,7 +253,10 @@ private void attemptTaskResumption(ScheduledTask task, TaskExecutor executor) { } /** - * Attempts to restart a task that can't be resumed + * Reschedules and executes a crashed task that cannot be resumed. + * + * @param task the task to restart + * @param executor the executor for the task type */ private void attemptTaskRestart(ScheduledTask task, TaskExecutor executor) { LOGGER.info("Node {} restarting crashed task: {}", nodeId, task.getItemId()); @@ -221,7 +267,7 @@ private void attemptTaskRestart(ScheduledTask task, TaskExecutor executor) { } /** - * Recovers tasks with expired locks that are not marked as running + * Releases expired locks on tasks that are not currently running. */ private void recoverLockedTasks() { List lockedTasks = schedulerService.findLockedTasks(); @@ -235,7 +281,9 @@ private void recoverLockedTasks() { } /** - * Recovers a single locked task + * Releases an expired lock and reschedules the task when appropriate. + * + * @param task the locked task to recover */ private void recoverLockedTask(ScheduledTask task) { lockManager.releaseLock(task); @@ -258,7 +306,10 @@ private void recoverLockedTask(ScheduledTask task) { } /** - * Determines if a crashed task should be restarted + * Returns whether a crashed task should be restarted instead of abandoned. + * + * @param task the crashed task + * @return {@code true} when the task should be restarted */ private boolean shouldRestartTask(ScheduledTask task) { // Don't restart one-shot tasks that have already started @@ -274,9 +325,11 @@ private boolean shouldRestartTask(ScheduledTask task) { return task.isEnabled(); } - /** - * Gets dependencies for a task + * Loads dependency tasks referenced by the given task. + * + * @param task the task whose dependencies are needed + * @return dependency tasks keyed by ID */ private Map getTaskDependencies(ScheduledTask task) { if (task.getDependsOn() == null || task.getDependsOn().isEmpty()) { @@ -294,7 +347,9 @@ private Map getTaskDependencies(ScheduledTask task) { } /** - * Update running task to crashed state + * Marks a running task as crashed and releases its lock. + * + * @param task the task to mark as crashed */ private void markAsCrashed(ScheduledTask task) { try { @@ -324,7 +379,9 @@ private void markAsCrashed(ScheduledTask task) { } /** - * Resets a task that has been in running state for too long + * Marks a stalled running task as failed after a timeout. + * + * @param task the stalled task */ private void resetStalledTask(ScheduledTask task) { try { diff --git a/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskStateManager.java b/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskStateManager.java index 1e909bd90..07c118c50 100644 --- a/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskStateManager.java +++ b/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskStateManager.java @@ -24,8 +24,9 @@ import java.util.*; /** - * Manages task state transitions and validation. - * This class centralizes all state-related logic for scheduled tasks. + * Enforces allowed {@link org.apache.unomi.api.tasks.ScheduledTask.TaskStatus} transitions. + * The scheduler calls this helper before running, completing, or recovering tasks so + * invalid state changes are rejected and logged consistently across nodes. */ public class TaskStateManager { private static final Logger LOGGER = LoggerFactory.getLogger(TaskStateManager.class); @@ -54,6 +55,13 @@ public enum TaskTransition { this.validStartStates = validStartStates; } + /** + * Returns whether a transition from one status to another is allowed. + * + * @param from the current status + * @param to the target status + * @return true if the transition is valid + */ public static boolean isValidTransition(TaskStatus from, TaskStatus to) { // Allow same state transitions during recovery if (from == to && from == TaskStatus.RUNNING) { @@ -66,7 +74,12 @@ public static boolean isValidTransition(TaskStatus from, TaskStatus to) { } /** - * Updates task state with validation and state-specific updates + * Updates task state with validation and state-specific updates. + * + * @param task the task to update + * @param newStatus the target status + * @param error optional error message + * @param nodeId the node performing the update */ public void updateTaskState(ScheduledTask task, TaskStatus newStatus, String error, String nodeId) { TaskStatus currentStatus = task.getStatus(); @@ -83,7 +96,11 @@ public void updateTaskState(ScheduledTask task, TaskStatus newStatus, String err } /** - * Validates a state transition + * Validates that the requested status change is allowed. + * + * @param currentStatus the current task status + * @param newStatus the requested status + * @throws IllegalStateException when the transition is not allowed */ private void validateStateTransition(TaskStatus currentStatus, TaskStatus newStatus) { if (currentStatus == TaskStatus.CANCELLED && newStatus == TaskStatus.CRASHED) { @@ -91,7 +108,7 @@ private void validateStateTransition(TaskStatus currentStatus, TaskStatus newSta String.format("Cannot recover a cancelled task: Invalid state transition from %s to %s", currentStatus, newStatus)); } - + if (!TaskTransition.isValidTransition(currentStatus, newStatus)) { throw new IllegalStateException( String.format("Invalid state transition from %s to %s", @@ -100,7 +117,11 @@ private void validateStateTransition(TaskStatus currentStatus, TaskStatus newSta } /** - * Updates state-specific fields based on the new status + * Updates fields that depend on the new task status. + * + * @param task the task being updated + * @param newStatus the new status + * @param nodeId the node performing the update */ private void updateStateSpecificFields(ScheduledTask task, TaskStatus newStatus, String nodeId) { switch (newStatus) { @@ -159,7 +180,11 @@ private Map getOrCreateStatusDetails(ScheduledTask task) { } /** - * Checks if a task can be rescheduled based on its dependencies + * Checks if a task can be rescheduled based on its dependencies. + * + * @param task the task to check + * @param dependencies dependency tasks keyed by ID + * @return true if all dependencies are completed */ public boolean canRescheduleTask(ScheduledTask task, Map dependencies) { if (task.getWaitingOnTasks() == null || task.getWaitingOnTasks().isEmpty()) { @@ -176,7 +201,9 @@ public boolean canRescheduleTask(ScheduledTask task, Map } /** - * Resets a task's waiting state and marks it as scheduled + * Resets a task's waiting state and marks it as scheduled. + * + * @param task the task to reset */ public void resetTaskToScheduled(ScheduledTask task) { task.setStatus(TaskStatus.SCHEDULED); @@ -185,7 +212,10 @@ public void resetTaskToScheduled(ScheduledTask task) { } /** - * Validates task configuration + * Validates task configuration. + * + * @param task the task to validate + * @param existingTasks known tasks keyed by ID */ public void validateTask(ScheduledTask task, Map existingTasks) { if (task.getTaskType() == null || task.getTaskType().trim().isEmpty()) { @@ -229,9 +259,10 @@ private void validateDependencies(ScheduledTask task, Map } /** - * Calculates the next execution time for a task - * @param task The task to calculate next execution for - * @param isRetry Whether this calculation is for a retry attempt + * Calculates and stores the next execution time for a task. + * + * @param task the task to schedule + * @param isRetry {@code true} when computing a retry delay */ public void calculateNextExecutionTime(ScheduledTask task, boolean isRetry) { long now = System.currentTimeMillis(); @@ -306,8 +337,9 @@ public void calculateNextExecutionTime(ScheduledTask task, boolean isRetry) { } /** - * Calculates the next execution time for a task (non-retry case) - * @param task The task to calculate next execution for + * Calculates and stores the next execution time for a normal (non-retry) run. + * + * @param task the task to schedule */ public void calculateNextExecutionTime(ScheduledTask task) { calculateNextExecutionTime(task, false); diff --git a/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskValidationManager.java b/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskValidationManager.java index ad5b3111b..8f1bf62d8 100644 --- a/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskValidationManager.java +++ b/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskValidationManager.java @@ -30,7 +30,10 @@ public class TaskValidationManager { private static final Logger LOGGER = LoggerFactory.getLogger(TaskValidationManager.class); /** - * Validates task configuration and dependencies + * Validates task configuration and dependencies. + * + * @param task the task to validate + * @param existingTasks known tasks keyed by ID */ public void validateTask(ScheduledTask task, Map existingTasks) { validateBasicConfiguration(task); @@ -137,7 +140,10 @@ private void validateExecutionConfiguration(ScheduledTask task) { } /** - * Validates a state transition + * Validates a state transition. + * + * @param task the task being updated + * @param newStatus the target status */ public void validateStateTransition(ScheduledTask task, ScheduledTask.TaskStatus newStatus) { ScheduledTask.TaskStatus currentStatus = task.getStatus(); @@ -173,7 +179,10 @@ private boolean isValidTransition(ScheduledTask.TaskStatus from, ScheduledTask.T } /** - * Validates task execution prerequisites + * Validates task execution prerequisites. + * + * @param task the task to execute + * @param nodeId the executing node ID */ public void validateExecutionPrerequisites(ScheduledTask task, String nodeId) { if (task.getStatus() != ScheduledTask.TaskStatus.SCHEDULED && diff --git a/services/src/main/java/org/apache/unomi/services/impl/tenants/TenantMigrationService.java b/services/src/main/java/org/apache/unomi/services/impl/tenants/TenantMigrationService.java index 2a345e2bd..7579e6348 100644 --- a/services/src/main/java/org/apache/unomi/services/impl/tenants/TenantMigrationService.java +++ b/services/src/main/java/org/apache/unomi/services/impl/tenants/TenantMigrationService.java @@ -25,6 +25,9 @@ import java.util.Arrays; import java.util.List; +/** + * Service for migrating tenant data between tenants. + */ public class TenantMigrationService { private static final Logger logger = LoggerFactory.getLogger(TenantMigrationService.class); @@ -32,14 +35,31 @@ public class TenantMigrationService { private PersistenceService persistenceService; private TenantService tenantService; + /** + * Sets the persistence service via Blueprint dependency injection. + * + * @param persistenceService the persistence service + */ public void setPersistenceService(PersistenceService persistenceService) { this.persistenceService = persistenceService; } + /** + * Sets the tenant service. + * + * @param tenantService the tenant service + */ public void setTenantService(TenantService tenantService) { this.tenantService = tenantService; } + /** + * Migrates data from one tenant to another. + * + * @param sourceTenantId the source tenant ID + * @param targetTenantId the target tenant ID + * @return true if migration succeeded + */ public boolean migrateTenant(String sourceTenantId, String targetTenantId) { try { // Verify tenants exist diff --git a/services/src/main/java/org/apache/unomi/services/impl/tenants/TenantSecurityService.java b/services/src/main/java/org/apache/unomi/services/impl/tenants/TenantSecurityService.java index 7ed5f704c..6d40b745e 100644 --- a/services/src/main/java/org/apache/unomi/services/impl/tenants/TenantSecurityService.java +++ b/services/src/main/java/org/apache/unomi/services/impl/tenants/TenantSecurityService.java @@ -21,22 +21,43 @@ import org.slf4j.LoggerFactory; /** - * Core tenant security service that handles tenant-specific security operations. + * Tenant security helper intended to handle tenant-specific security operations. * Rate limiting and IP filtering are handled by Apache CXF. + *

+ * This class is not currently registered as an OSGi service (no {@code @Component} + * annotation, no Blueprint bean) and is not instantiated anywhere in the runtime. + * Live API key validation for incoming requests is performed instead by + * {@code org.apache.unomi.rest.authentication.AuthenticationFilter}. */ public class TenantSecurityService { private static final Logger logger = LoggerFactory.getLogger(TenantSecurityService.class); private ConfigurationAdmin configAdmin; + /** + * Sets the OSGi configuration admin service. + * + * @param configAdmin the configuration admin + */ public void setConfigAdmin(ConfigurationAdmin configAdmin) { this.configAdmin = configAdmin; } + /** + * Activation hook that loads tenant security configuration. + * Not currently invoked by the OSGi container; see the class-level note. + */ public void activate() { loadSecurityConfigurations(); } + /** + * Validates an incoming tenant API request. + * + * @param tenantId the tenant ID + * @param apiKey the API key + * @return true if the request is allowed + */ public boolean validateRequest(String tenantId, String apiKey) { // Validate API key if (!validateApiKey(tenantId, apiKey)) { @@ -48,8 +69,10 @@ public boolean validateRequest(String tenantId, String apiKey) { } private boolean validateApiKey(String tenantId, String apiKey) { - // Implementation of API key validation - return true; // TODO: Implement actual validation + // TODO: Implement actual validation. This method is currently unreachable since + // TenantSecurityService is not wired into the OSGi runtime (see class-level Javadoc); + // do not treat this stub as evidence that API key validation happens here. + return true; } private void loadSecurityConfigurations() { diff --git a/services/src/main/java/org/apache/unomi/services/impl/tenants/TenantServiceImpl.java b/services/src/main/java/org/apache/unomi/services/impl/tenants/TenantServiceImpl.java index 363a9a70c..0359d9dbf 100644 --- a/services/src/main/java/org/apache/unomi/services/impl/tenants/TenantServiceImpl.java +++ b/services/src/main/java/org/apache/unomi/services/impl/tenants/TenantServiceImpl.java @@ -31,6 +31,9 @@ import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; +/** + * Default implementation of {@link TenantService}. + */ public class TenantServiceImpl implements TenantService { private static final Logger LOGGER = LoggerFactory.getLogger(TenantServiceImpl.class); private static final int MAX_TENANT_ID_LENGTH = 32; @@ -41,23 +44,48 @@ public class TenantServiceImpl implements TenantService { private ExecutionContextManager executionContextManager; private SecretHashService secretHashService; + /** + * Sets the persistence service via Blueprint dependency injection. + * + * @param persistenceService the persistence service + */ public void setPersistenceService(PersistenceService persistenceService) { this.persistenceService = persistenceService; } + /** + * Sets the execution context manager. + * + * @param executionContextManager the execution context manager + */ public void setExecutionContextManager(ExecutionContextManager executionContextManager) { this.executionContextManager = executionContextManager; } + /** + * Sets the secret hash service for API key hashing. + * + * @param secretHashService the secret hash service + */ public void setSecretHashService(SecretHashService secretHashService) { this.secretHashService = secretHashService; } + /** + * Registers a tenant lifecycle listener. + * + * @param listener the listener to add + */ public void bindListener(TenantLifecycleListener listener) { lifecycleListeners.add(listener); LOGGER.debug("Added tenant lifecycle listener: {}", listener.getClass().getName()); } + /** + * Unregisters a tenant lifecycle listener. + * + * @param listener the listener to remove + */ public void unbindListener(TenantLifecycleListener listener) { if (listener != null) { lifecycleListeners.remove(listener); diff --git a/services/src/main/java/org/apache/unomi/services/impl/tenants/TenantUsageServiceImpl.java b/services/src/main/java/org/apache/unomi/services/impl/tenants/TenantUsageServiceImpl.java index 5a63e2b22..bd53c6f97 100644 --- a/services/src/main/java/org/apache/unomi/services/impl/tenants/TenantUsageServiceImpl.java +++ b/services/src/main/java/org/apache/unomi/services/impl/tenants/TenantUsageServiceImpl.java @@ -57,6 +57,9 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Pattern; +/** + * Default implementation of {@link TenantUsageService}. + */ public class TenantUsageServiceImpl implements TenantUsageService { private static final Logger logger = LoggerFactory.getLogger(TenantUsageServiceImpl.class); @@ -72,27 +75,53 @@ public class TenantUsageServiceImpl implements TenantUsageService { private ScheduledExecutorService executor; private volatile boolean shutdownNow = false; + /** + * Sets the persistence service via Blueprint dependency injection. + * + * @param persistenceService the persistence service + */ public void setPersistenceService(PersistenceService persistenceService) { this.persistenceService = persistenceService; } + /** + * Sets the tenant service. + * + * @param tenantService the tenant service + */ public void setTenantService(TenantService tenantService) { this.tenantService = tenantService; } + /** + * Sets the definitions service. + * + * @param definitionsService the definitions service + */ public void setDefinitionsService(DefinitionsService definitionsService) { this.definitionsService = definitionsService; } + /** + * Sets the execution context manager. + * + * @param contextManager the execution context manager + */ public void setContextManager(ExecutionContextManager contextManager) { this.contextManager = contextManager; } + /** + * Blueprint activate hook; starts usage metrics collection. + */ public void activate() { shutdownNow = false; startMetricsCollection(); } + /** + * Blueprint deactivate hook; stops usage metrics collection. + */ public void deactivate() { shutdownNow = true; stopMetricsCollection(); diff --git a/tracing/tracing-api/src/main/java/org/apache/unomi/tracing/api/TraceNode.java b/tracing/tracing-api/src/main/java/org/apache/unomi/tracing/api/TraceNode.java index 42eaf755a..683d75bc8 100644 --- a/tracing/tracing-api/src/main/java/org/apache/unomi/tracing/api/TraceNode.java +++ b/tracing/tracing-api/src/main/java/org/apache/unomi/tracing/api/TraceNode.java @@ -24,8 +24,10 @@ import java.util.List; /** - * Represents a node in the request tracing tree structure. - * Each node contains information about an operation, its timing, and any child operations. + * One node in the hierarchical request trace returned to operators. + * Each node records an operation name, timing, optional context/result strings, + * log lines, and nested child nodes. The tracing service assembles these into + * a tree attached to context and event processing responses. */ @JsonIgnoreProperties(ignoreUnknown = true) public class TraceNode implements Serializable { @@ -38,83 +40,181 @@ public class TraceNode implements Serializable { private List traces; private List children; + /** + * Creates an empty trace node with empty trace and child lists. + */ public TraceNode() { this.traces = new ArrayList<>(); this.children = new ArrayList<>(); } + /** + * Returns the type of operation represented by this node. + * + * @return the operation type + */ public String getOperationType() { return operationType; } + /** + * Sets the type of operation represented by this node. + * + * @param operationType the operation type + */ public void setOperationType(String operationType) { this.operationType = operationType; } + /** + * Returns the human-readable description of this operation. + * + * @return the operation description + */ public String getDescription() { return description; } + /** + * Sets the human-readable description of this operation. + * + * @param description the operation description + */ public void setDescription(String description) { this.description = description; } + /** + * Returns additional context information for this operation. + * + * @return the operation context + */ public String getContext() { return context; } + /** + * Sets additional context information for this operation. + * + * @param context the operation context + */ public void setContext(String context) { this.context = context; } + /** + * Returns the result summary for this operation. + * + * @return the operation result + */ public String getResult() { return result; } + /** + * Sets the result summary for this operation. + * + * @param result the operation result + */ public void setResult(String result) { this.result = result; } + /** + * Returns the start time of this operation in milliseconds. + * + * @return the start time + */ public long getStartTime() { return startTime; } + /** + * Sets the start time of this operation in milliseconds. + * + * @param startTime the start time + */ public void setStartTime(long startTime) { this.startTime = startTime; } + /** + * Returns the end time of this operation in milliseconds. + * + * @return the end time + */ public long getEndTime() { return endTime; } + /** + * Sets the end time of this operation in milliseconds. + * + * @param endTime the end time + */ public void setEndTime(long endTime) { this.endTime = endTime; } + /** + * Returns the operation duration in milliseconds. + * + * @return duration as {@code endTime - startTime}, or zero if end time precedes start time + */ public long getDuration() { return Math.max(0, endTime - startTime); } + /** + * Adds a trace message to this node. + * + * @param trace the trace message to add + */ public void addTrace(String trace) { this.traces.add(trace); } + /** + * Returns the trace messages recorded for this node. + * + * @return an unmodifiable view of trace messages + */ public List getTraces() { return Collections.unmodifiableList(traces); } + /** + * Replaces the trace messages for this node. + * + * @param traces the new trace message list + */ public void setTraces(List traces) { this.traces = traces; } + /** + * Adds a child trace node under this node. + * + * @param child the child trace node to add + */ public void addChild(TraceNode child) { this.children.add(child); } + /** + * Returns the child trace nodes. + * + * @return an unmodifiable view of child nodes + */ public List getChildren() { return Collections.unmodifiableList(children); } + /** + * Replaces the child trace nodes. + * + * @param children the new child node list + */ public void setChildren(List children) { this.children = children; }