Skip to content

Timestamp attributes conversion logic Improvements.#4599

Open
Thisara-Welmilla wants to merge 1 commit into
wso2:4.12.xfrom
Thisara-Welmilla:improve-timestamp-convertion-logic-fix
Open

Timestamp attributes conversion logic Improvements.#4599
Thisara-Welmilla wants to merge 1 commit into
wso2:4.12.xfrom
Thisara-Welmilla:improve-timestamp-convertion-logic-fix

Conversation

@Thisara-Welmilla

Copy link
Copy Markdown

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Refactor

    • Enhanced error handling in user attribute processing to ensure failures are properly detected and reported through exception propagation.
    • Consolidated LDAP timestamp conversion logic into centralized utilities for more consistent date/time handling across user stores.
  • Tests

    • Updated timestamp conversion tests to reflect refactored utility functions.

Walkthrough

The PR refactors LDAP timestamp processing to centralize conversion logic in LDAPUtil and introduces exception-throwing API variants across manager classes. New public methods in LDAPUtil handle timestamp format inference and conversion to ISO-8601 instant strings. Manager classes gain exception-throwing ...WithException method variants alongside deprecated non-throwing predecessors, with all call sites updated accordingly. Tests shift from reflection-based private method invocation to direct LDAPUtil calls.

Changes

LDAP Timestamp Conversion Centralization with Exception-Throwing API

Layer / File(s) Summary
LDAPUtil timestamp conversion utilities
core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/util/LDAPUtil.java
Adds regex Pattern constants and two new public static methods: convertToStandardTimeFormat(RealmConfiguration, String) converts LDAP timestamps to ISO-8601 instant strings with support for configured custom patterns and automatic format derivation; deriveTimestampFormat(String) inspects input against regex patterns and returns appropriate DateTimeFormatter pattern strings. Parsing failures and unsupported formats surface as UserStoreException.
AbstractUserStoreManager exception-throwing API and call sites
core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/common/AbstractUserStoreManager.java
Adds protected exception-throwing methods processAttributesAfterRetrievalWithException(String, Map, String) and processAttributesAfterRetrievalWithIDWithException(String, Map, String) as new API variants; marks processAttributesAfterRetrievalWithID as @Deprecated with guidance; delegates new methods to existing non-throwing variants. Updates four call sites (single-user and multi-user flows, username-based and ID-based retrieval) to invoke the new ...WithException variants.
ReadOnlyLDAPUserStoreManager exception override and timestamp refactoring
core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/ldap/ReadOnlyLDAPUserStoreManager.java
Adds imports for ConfigurationContext and MultitenantUtils. Implements processAttributesAfterRetrievalWithException override with exception-throwing behavior and a non-throwing wrapper that logs UserStoreException. Refactors timestamp attribute conversion from stream-based to explicit loop using LDAPUtil.convertToStandardTimeFormat. Marks convertDateFormatFromLDAP(String) as @Deprecated pointing to LDAPUtil equivalent.
UniqueIDReadOnlyLDAPUserStoreManager removal of local timestamp logic and exception API
core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/ldap/UniqueIDReadOnlyLDAPUserStoreManager.java
Removes unused Function import and simplifies regex timestamp-format patterns. Delegates local convertToStandardTimeFormat and deriveTimestampFormat implementations to LDAPUtil equivalents. Introduces processAttributesAfterRetrievalWithIDWithException override for exception-throwing behavior with non-throwing wrapper that logs. Refactors attribute and group date timestamp conversions to use LDAPUtil.convertToStandardTimeFormat via explicit loops; updates format inference to use LDAPUtil.deriveTimestampFormat.
UniqueIDReadOnlyLDAPUserStoreManagerTest direct LDAPUtil calls
core/org.wso2.carbon.user.core/src/test/java/org/wso2/carbon/user/core/ldap/UniqueIDReadOnlyLDAPUserStoreManagerTest.java
Imports LDAPUtil to enable direct calls instead of reflection. Simplifies setUp() to only initialize mocks without reflection-based field injection; removes throws Exception from signature. Updates all timestamp-format test cases to call LDAPUtil.convertToStandardTimeFormat directly and catch UserStoreException directly instead of unwrapping reflective-invocation exceptions. Maintains same test coverage for supported, custom-pattern, edge-case, and unsupported-format scenarios.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is incomplete, providing only a GitHub issue link without addressing most required template sections like Purpose, Goals, Approach, User Stories, Release Notes, Documentation, Testing, and Security Checks. Complete the PR description following the repository template, including Purpose, Goals, Approach, automation tests, security checks, and other required sections to provide sufficient context for reviewers.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: centralizing and improving timestamp conversion logic across multiple files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.43.0)
core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/common/AbstractUserStoreManager.java

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@wso2-engineering wso2-engineering Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Agent Log Improvement Checklist

⚠️ Warning: AI-Generated Review Comments

  • The log-related comments and suggestions in this review were generated by an AI tool to assist with identifying potential improvements. Purpose of reviewing the code for log improvements is to improve the troubleshooting capabilities of our products.
  • Please make sure to manually review and validate all suggestions before applying any changes. Not every code suggestion would make sense or add value to our purpose. Therefore, you have the freedom to decide which of the suggestions are helpful.

✅ Before merging this pull request:

  • Review all AI-generated comments for accuracy and relevance.
  • Complete and verify the table below. We need your feedback to measure the accuracy of these suggestions and the value they add. If you are rejecting a certain code suggestion, please mention the reason briefly in the suggestion for us to capture it.
Comment Accepted (Y/N) Reason
#### Log Improvement Suggestion No: 1
#### Log Improvement Suggestion No: 2
#### Log Improvement Suggestion No: 3
#### Log Improvement Suggestion No: 4
#### Log Improvement Suggestion No: 5
#### Log Improvement Suggestion No: 6
#### Log Improvement Suggestion No: 7
#### Log Improvement Suggestion No: 8

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/util/LDAPUtil.java (1)

159-161: 💤 Low value

Include the timestamp value in parse error messages for easier debugging.

Lines 160 and 171 include only the pattern in the error message, but not the actual timestamp that failed to parse. Line 174 does include the timestamp. Consider making these consistent.

Proposed improvement
             } catch (DateTimeParseException e) {
-                throw new UserStoreException("Invalid timestamp format for pattern: " + userstoreTimestampFormat, e);
+                throw new UserStoreException("Invalid timestamp '" + dateTimestamp + "' for pattern: " + userstoreTimestampFormat, e);
             }
             } catch (DateTimeParseException e) {
-                throw new UserStoreException("Invalid timestamp format for pattern: " + derivedTimeStampPattern, e);
+                throw new UserStoreException("Invalid timestamp '" + dateTimestamp + "' for pattern: " + derivedTimeStampPattern, e);
             }

Also applies to: 170-172

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/util/LDAPUtil.java`
around lines 159 - 161, Update the DateTimeParseException handling in LDAPUtil
to include the actual timestamp string alongside the pattern in the thrown
UserStoreException message: use the existing variables userstoreTimestamp and
userstoreTimestampFormat (or similarly named locals in the same method) to build
the message, e.g. "Invalid timestamp '...'' for pattern '...'", and apply the
same change to both catch blocks referenced (the one currently throwing "Invalid
timestamp format for pattern: " + userstoreTimestampFormat and the similar block
at lines ~170-172) so the error consistently contains the failing timestamp and
the pattern while still passing the original exception as the cause.
core/org.wso2.carbon.user.core/src/test/java/org/wso2/carbon/user/core/ldap/UniqueIDReadOnlyLDAPUserStoreManagerTest.java (1)

148-155: 💤 Low value

Tautological assertion provides no value.

The assertion assertTrue(e instanceof UserStoreException, ...) inside a catch (UserStoreException e) block is always true by definition. Consider asserting on the exception message content instead, or removing the assertion entirely since the test is designed to pass regardless of whether the exception is thrown.

🔧 Suggested simplification
         try {
             LDAPUtil.convertToStandardTimeFormat(realmConfig, "20250813145607+05");
             // If this doesn't throw an exception, it's supported.
         } catch (UserStoreException e) {
             // Expected - 2-digit timezone not supported by current regex.
-            assertTrue(e instanceof UserStoreException,
-                    "Should throw UserStoreException for 2-digit timezone");
+            // Exception thrown as expected for unsupported 2-digit timezone format.
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/org.wso2.carbon.user.core/src/test/java/org/wso2/carbon/user/core/ldap/UniqueIDReadOnlyLDAPUserStoreManagerTest.java`
around lines 148 - 155, The catch block in
UniqueIDReadOnlyLDAPUserStoreManagerTest (catch (UserStoreException e)) contains
a tautological assertion assertTrue(e instanceof UserStoreException, ...) which
is always true; remove that assertion and either (a) assert on the exception
content (e.g., check e.getMessage() contains the expected regex/timezone error)
or (b) simply leave the catch empty/comment that the exception is expected so
the test passes; locate the LDAPUtil.convertToStandardTimeFormat call and
replace the tautological assert with a meaningful assertion on e.getMessage() or
delete the assertion entirely.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/util/LDAPUtil.java`:
- Around line 31-32: The constant ONE_DIGIT_FRACTION_TIMESTAMP_PATTERN is dead
code and duplicates ONE_DIGIT_FRACTION_WITH_MINUTES_TIMESTAMP_PATTERN; remove
the ONE_DIGIT_FRACTION_TIMESTAMP_PATTERN declaration from LDAPUtil and ensure
deriveTimestampFormat and any other methods reference the remaining
ONE_DIGIT_FRACTION_WITH_MINUTES_TIMESTAMP_PATTERN (or consolidate to that single
constant) so there are no unresolved references.
- Around line 207-210: The minute-fraction patterns (returned as
"uuuuMMddHHmm,SX"/"uuuuMMddHHmm.SX") are wrong because DateTimeFormatter 'S' is
fraction-of-second; update LDAPUtil to stop returning these misleading patterns
for ONE_DIGIT_FRACTION_WITH_MINUTES_TIMESTAMP_PATTERN and instead handle them
explicitly in the parsing logic (e.g., in the method that builds/parses
timestamps such as parseGeneralizedTime). Specifically, when
ONE_DIGIT_FRACTION_WITH_MINUTES_TIMESTAMP_PATTERN matches, parse the base
timestamp with "uuuuMMddHHmm", extract the fraction digits after ',' or '.',
convert that fraction-of-minute into seconds/nanos by computing nanos =
(fraction / 10^len) * 60 * 1_000_000_000 (or equivalent integer math to avoid
loss), then apply those seconds/nanos to the parsed LocalDateTime/Instant; do
not rely on a DateTimeFormatter pattern containing 'S'. Ensure you reference and
change handling for both comma and dot separators (',' and '.') and update any
tests or callers that expect the old pattern behavior.

---

Nitpick comments:
In
`@core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/util/LDAPUtil.java`:
- Around line 159-161: Update the DateTimeParseException handling in LDAPUtil to
include the actual timestamp string alongside the pattern in the thrown
UserStoreException message: use the existing variables userstoreTimestamp and
userstoreTimestampFormat (or similarly named locals in the same method) to build
the message, e.g. "Invalid timestamp '...'' for pattern '...'", and apply the
same change to both catch blocks referenced (the one currently throwing "Invalid
timestamp format for pattern: " + userstoreTimestampFormat and the similar block
at lines ~170-172) so the error consistently contains the failing timestamp and
the pattern while still passing the original exception as the cause.

In
`@core/org.wso2.carbon.user.core/src/test/java/org/wso2/carbon/user/core/ldap/UniqueIDReadOnlyLDAPUserStoreManagerTest.java`:
- Around line 148-155: The catch block in
UniqueIDReadOnlyLDAPUserStoreManagerTest (catch (UserStoreException e)) contains
a tautological assertion assertTrue(e instanceof UserStoreException, ...) which
is always true; remove that assertion and either (a) assert on the exception
content (e.g., check e.getMessage() contains the expected regex/timezone error)
or (b) simply leave the catch empty/comment that the exception is expected so
the test passes; locate the LDAPUtil.convertToStandardTimeFormat call and
replace the tautological assert with a meaningful assertion on e.getMessage() or
delete the assertion entirely.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 2c84ce22-a6e7-41d6-8eb0-02e1fa92a7ce

📥 Commits

Reviewing files that changed from the base of the PR and between 1e87b3c and 0f1144c.

📒 Files selected for processing (5)
  • core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/common/AbstractUserStoreManager.java
  • core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/ldap/ReadOnlyLDAPUserStoreManager.java
  • core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/ldap/UniqueIDReadOnlyLDAPUserStoreManager.java
  • core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/util/LDAPUtil.java
  • core/org.wso2.carbon.user.core/src/test/java/org/wso2/carbon/user/core/ldap/UniqueIDReadOnlyLDAPUserStoreManagerTest.java

Comment on lines +31 to +32
private static final Pattern ONE_DIGIT_FRACTION_TIMESTAMP_PATTERN =
Pattern.compile("^\\d{12}[,\\.]\\d{1}([-+]\\d{4}|Z)$");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Dead code: ONE_DIGIT_FRACTION_TIMESTAMP_PATTERN is defined but never used.

This pattern is identical to ONE_DIGIT_FRACTION_WITH_MINUTES_TIMESTAMP_PATTERN (line 35-36) and is never referenced in deriveTimestampFormat. Remove this unused constant.

Proposed fix
     private static final Pattern TWO_DIGIT_FRACTION_TIMESTAMP_PATTERN =
             Pattern.compile("^\\d{14}[,\\.]\\d{2}([-+]\\d{4}|Z)$");
-    private static final Pattern ONE_DIGIT_FRACTION_TIMESTAMP_PATTERN =
-            Pattern.compile("^\\d{12}[,\\.]\\d{1}([-+]\\d{4}|Z)$");
     private static final Pattern ONE_DIGIT_FRACTION_WITH_SECONDS_TIMESTAMP_PATTERN =
             Pattern.compile("^\\d{14}[,\\.]\\d{1}([-+]\\d{4}|Z)$");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static final Pattern ONE_DIGIT_FRACTION_TIMESTAMP_PATTERN =
Pattern.compile("^\\d{12}[,\\.]\\d{1}([-+]\\d{4}|Z)$");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/util/LDAPUtil.java`
around lines 31 - 32, The constant ONE_DIGIT_FRACTION_TIMESTAMP_PATTERN is dead
code and duplicates ONE_DIGIT_FRACTION_WITH_MINUTES_TIMESTAMP_PATTERN; remove
the ONE_DIGIT_FRACTION_TIMESTAMP_PATTERN declaration from LDAPUtil and ensure
deriveTimestampFormat and any other methods reference the remaining
ONE_DIGIT_FRACTION_WITH_MINUTES_TIMESTAMP_PATTERN (or consolidate to that single
constant) so there are no unresolved references.

Comment on lines +207 to +210
// Case 5: 12 digits with 1-digit fraction (minutes precision).
else if (ONE_DIGIT_FRACTION_WITH_MINUTES_TIMESTAMP_PATTERN.matcher(timestamp).matches()) {
return timestamp.contains(",") ? "uuuuMMddHHmm,SX" : "uuuuMMddHHmm.SX";
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Java DateTimeFormatter fraction-of-second S pattern without seconds field

💡 Result:

In the Java java.time.format.DateTimeFormatter, the pattern letter 'S' is used to represent the fraction-of-second field [1][2]. While it is common to use 'S' in conjunction with the seconds field (e.g., "HH:mm:ss.SSS"), the pattern syntax does not strictly require the seconds field to be present to use 'S' [1][3]. However, if you include 'S' in a pattern without 's' (second-of-minute), the formatter will still attempt to format or parse the nano-of-second field [1][2]. Because 'S' represents a fraction, it effectively works with the time-of-day [1][4]. If your time object does not contain information down to the nanosecond level, or if you omit seconds but expect a meaningful fractional representation, ensure your input data supports the precision you are requesting [5]. To implement this, you can simply include 'S' in your pattern string [5]. For example: DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm.SSS"); This will output the hour, minute, and a three-digit fractional second [5]. If you require more complex behavior—such as making the fractional part optional or ensuring it only appears if a value exists—it is recommended to use the DateTimeFormatterBuilder rather than a simple pattern string [6][5]. This allows for granular control using the appendFraction method [6][7]: DateTimeFormatter formatter = new DateTimeFormatterBuilder.appendPattern("HH:mm").appendFraction(ChronoField.NANO_OF_SECOND, 0, 3, true) // min 0, max 3, include decimal point.toFormatter; This approach avoids the constraints of fixed pattern letters and provides precise control over how fractional seconds are handled when seconds or other fields might be omitted [7][5].

Citations:


🌐 Web query:

RFC 4517 Generalized Time fraction after minutes without seconds semantics fraction of minute

💡 Result:

Under RFC 4517, the GeneralizedTime syntax allows for an optional fraction of a minute when the minute component is present but the second and leap-second components are omitted [1][2]. Specifically, the ABNF definition for GeneralizedTime includes an optional fraction component [3][4]. The semantic interpretation of this fraction depends on which time components are included in the string: - If the minute component is omitted, the fraction represents a fraction of an hour [1][2]. - If the minute component is present, but the second and leap-second components are omitted, the fraction represents a fraction of a minute [1][2]. - If both the minute and second (or leap-second) components are present, the fraction represents a fraction of a second [1][2]. Therefore, a fraction appended after the minutes (without seconds) is semantically valid and represents a fraction of a minute [1][5][2].

Citations:


Fix GeneralizedTime minute-fraction parsing (uuuuMMddHHmm,SX / uuuuMMddHHmm.SX) in LDAPUtil.

RFC 4517 specifies that when only minutes are present, the optional fraction is a fraction of a minute; however, Java DateTimeFormatter pattern S is fraction-of-second (nano-of-second). Using uuuuMMddHHmm,SX/uuuuMMddHHmm.SX therefore misinterprets the LDAP fraction and can produce incorrect timestamps—convert fraction-of-minute to seconds/nanos explicitly (custom parsing/scaling) instead of relying on S.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/util/LDAPUtil.java`
around lines 207 - 210, The minute-fraction patterns (returned as
"uuuuMMddHHmm,SX"/"uuuuMMddHHmm.SX") are wrong because DateTimeFormatter 'S' is
fraction-of-second; update LDAPUtil to stop returning these misleading patterns
for ONE_DIGIT_FRACTION_WITH_MINUTES_TIMESTAMP_PATTERN and instead handle them
explicitly in the parsing logic (e.g., in the method that builds/parses
timestamps such as parseGeneralizedTime). Specifically, when
ONE_DIGIT_FRACTION_WITH_MINUTES_TIMESTAMP_PATTERN matches, parse the base
timestamp with "uuuuMMddHHmm", extract the fraction digits after ',' or '.',
convert that fraction-of-minute into seconds/nanos by computing nanos =
(fraction / 10^len) * 60 * 1_000_000_000 (or equivalent integer math to avoid
loss), then apply those seconds/nanos to the parsed LocalDateTime/Instant; do
not rely on a DateTimeFormatter pattern containing 'S'. Ensure you reference and
change handling for both comma and dot separators (',' and '.') and update any
tests or callers that expect the old pattern behavior.

@jenkins-is-staging

Copy link
Copy Markdown

PR builder started
Link: https://github.com/wso2/product-is/actions/runs/26865988651

@jenkins-is-staging

Copy link
Copy Markdown

PR builder completed
Link: https://github.com/wso2/product-is/actions/runs/26865988651
Status: success

@jenkins-is-staging jenkins-is-staging left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving the pull request based on the successful pr build https://github.com/wso2/product-is/actions/runs/26865988651

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants