Skip to content

Conversation

dinesh-beehyv
Copy link
Contributor

@dinesh-beehyv dinesh-beehyv commented Jan 17, 2025

Made changes to prevent duplicate records in the OBD file when we receive an update from RCH for the LMP date.

Summary by CodeRabbit

  • New Features

    • Added support for including a service ID in call summary records, enabling more detailed tracking and reporting.
    • Introduced the ability to update call retry information for subscriptions, allowing for more flexible management of call retries.
    • Added a utility to extract the day of the week from a timestamp for improved scheduling and reporting.
  • Improvements

    • Refined logic for updating or deleting call retry records based on subscriber data changes, improving accuracy in retry handling.
    • Updated modification dates for all active subscriptions when an existing subscription is returned, ensuring more consistent record-keeping.
    • Enhanced call retry synchronization with subscriber contact details during rescheduling.
    • Improved failed call handling with route-based differentiation and logging for better diagnostics.

Copy link

coderabbitai bot commented Jan 31, 2025

Walkthrough

The changes introduce a new serviceId field to the CallSummaryRecordDto and update related methods and constructors to handle it. Logging and conditional logic are enhanced in several service implementation classes for better traceability and refined call retry handling. A new utility method for extracting the day of the week from a timestamp is added.

Changes

File(s) Change Summary
imi/.../CallSummaryRecord.java toDto() method now includes serviceId in CallSummaryRecordDto construction.
kilkari/.../CallSummaryRecordDto.java Added serviceId field, updated constructor, getters/setters, and static methods to handle serviceId.
kilkari/.../SubscriptionService.java Added updateCallRetry(String subscriptionId, Long msisdn) method to interface.
kilkari/.../CsrServiceImpl.java Added logging, new extractRouteNumber(String serviceId) method, and enhanced control flow.
kilkari/.../SubscriberServiceImpl.java Enhanced conditional logic and logging for call retry handling and subscription updates.
kilkari/.../SubscriptionServiceImpl.java Implemented updateCallRetry, allowing updates to msisdn in CallRetry records.
props/.../DayOfTheWeek.java Added getDayOfTheWeekFromTimestamp(String timestamp) static method to enum.

Sequence Diagram(s)

sequenceDiagram
    participant CallSummaryRecord
    participant CallSummaryRecordDto

    CallSummaryRecord->>CallSummaryRecordDto: toDto() (now includes serviceId)
Loading
sequenceDiagram
    participant Service
    participant SubscriptionServiceImpl
    participant CallRetryDataService

    Service->>SubscriptionServiceImpl: updateCallRetry(subscriptionId, msisdn)
    SubscriptionServiceImpl->>CallRetryDataService: findBySubscriptionId(subscriptionId)
    CallRetryDataService-->>SubscriptionServiceImpl: CallRetry
    SubscriptionServiceImpl->>CallRetryDataService: update(msisdn)
Loading
sequenceDiagram
    participant DayOfTheWeek
    participant Client

    Client->>DayOfTheWeek: getDayOfTheWeekFromTimestamp(timestamp)
    DayOfTheWeek-->>Client: DayOfTheWeek enum value
Loading

Poem

In fields of code where carrots grow,
A serviceId now joins the show!
With logging bright and dates in check,
Retries updated, no call a wreck.
Days of week from timestamps bloom—
The system hops with extra room!
🥕✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5d21f8c and f5c16c1.

📒 Files selected for processing (2)
  • kilkari/src/main/java/org/motechproject/nms/kilkari/service/impl/CsrServiceImpl.java (5 hunks)
  • kilkari/src/main/java/org/motechproject/nms/kilkari/service/impl/SubscriberServiceImpl.java (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • kilkari/src/main/java/org/motechproject/nms/kilkari/service/impl/CsrServiceImpl.java
  • kilkari/src/main/java/org/motechproject/nms/kilkari/service/impl/SubscriberServiceImpl.java
✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🔭 Outside diff range comments (1)
kilkari/src/main/java/org/motechproject/nms/kilkari/service/impl/CsrServiceImpl.java (1)

Line range hint 150-251: Refactor method for better readability and maintainability.

The doReschedule method has become complex with multiple nested conditions and mixed responsibilities. Consider:

  1. Extracting the retry logic into separate methods
  2. Using early returns to reduce nesting
  3. Moving logging to a debug level and using structured logging

Example refactor for better structure:

 private void doReschedule(Subscription subscription, CallRetry existingCallRetry, CallSummaryRecordDto csrDto) {
     boolean invalidNr = StatusCode.fromInt(csrDto.getStatusCode()).equals(StatusCode.OBD_FAILED_INVALIDNUMBER);
-    LOGGER.info("inside doreshedule method");
-    LOGGER.info("this is the data inside: {},{},{]",subscription,existingCallRetry, csrDto);
+    LOGGER.debug("Processing reschedule - subscription: {}, existingCallRetry: {}, csrDto: {}", 
+        subscription, existingCallRetry, csrDto);

     if (shouldCreateNewRetry(subscription, existingCallRetry)) {
         createNewRetry(subscription, csrDto, invalidNr);
         return;
     }

     if (shouldStopRetrying(subscription, existingCallRetry)) {
         handleLastRetry(subscription, existingCallRetry, csrDto);
         return;
     }

     updateExistingRetry(existingCallRetry, invalidNr);
 }

 private boolean shouldCreateNewRetry(Subscription subscription, CallRetry existingCallRetry) {
     return existingCallRetry == null && SubscriptionStatus.ACTIVE.equals(subscription.getStatus());
 }
🧹 Nitpick comments (5)
kilkari/src/main/java/org/motechproject/nms/kilkari/dto/CallSummaryRecordDto.java (1)

182-183: LGTM! Parameter mapping methods updated consistently.

The fromParams and toParams methods are correctly updated to handle the new serviceId field.

However, consider adding null checks for serviceId in fromParams to prevent potential NullPointerException.

 @Ignore
 public static CallSummaryRecordDto fromParams(Map<String, Object> params) {
     CallSummaryRecordDto csr;
     csr = new CallSummaryRecordDto(
             (String) params.get("subscriptionId"),
             (int) params.get("statusCode"),
             (int) params.get("finalStatus"),
             (String) params.get("contentFileName"),
             (String) params.get("weekId"),
             (String) params.get("languageCode"),
             (String) params.get("circleName"),
             (String) params.get("targetFileTimeStamp"),
             (Boolean) params.get("opt_in_call_eligibility"),
             (String) params.get("opt_in_input"),
-            (String) params.get("serviceId")
+            params.containsKey("serviceId") ? (String) params.get("serviceId") : null
     );
     return csr;
 }

Also applies to: 201-201

kilkari/src/main/java/org/motechproject/nms/kilkari/service/impl/SubscriptionServiceImpl.java (1)

Line range hint 1020-1036: Consider using bulk update for modification dates.

While the code correctly updates modification dates, consider using a bulk update operation for better performance when dealing with multiple active subscriptions.

-Set<Subscription> activeSubscriptions = subscriber.getActiveAndPendingSubscriptions();
-if (activeSubscriptions != null && !activeSubscriptions.isEmpty()) {
-    for(Subscription sub : activeSubscriptions ){
-        sub.setModificationDate(DateTime.now());
-    }
-}
-subscription.setModificationDate(DateTime.now());
+DateTime now = DateTime.now();
+Set<Subscription> activeSubscriptions = subscriber.getActiveAndPendingSubscriptions();
+if (activeSubscriptions != null && !activeSubscriptions.isEmpty()) {
+    subscriptionDataService.bulkUpdate("modificationDate", now, activeSubscriptions);
+}
+subscription.setModificationDate(now);
kilkari/src/main/java/org/motechproject/nms/kilkari/service/impl/SubscriberServiceImpl.java (1)

409-427: Improve logging statements using parameterized logging.

The logging statements should use parameterized logging for better performance and readability.

-LOGGER.info("1st value: {}",subscriberByRchId.getLastMenstrualPeriod().getDayOfYear());
-LOGGER.info("second value: {}",lmp.getDayOfYear());
-LOGGER.info("third value: {}",subscriberByRchId.getLastMenstrualPeriod().getYear());
-LOGGER.info("second value: {}",lmp.getYear());
-LOGGER.info("this is the first case: {}",(subscriberByRchId.getLastMenstrualPeriod().getDayOfYear() == lmp.getDayOfYear()));
-LOGGER.info("this is the second case2: {}",(subscriberByRchId.getLastMenstrualPeriod().getYear() == lmp.getYear()));
+LOGGER.info("LMP comparison - Current: day={}, year={}, New: day={}, year={}, DayMatch={}, YearMatch={}",
+    subscriberByRchId.getLastMenstrualPeriod().getDayOfYear(),
+    subscriberByRchId.getLastMenstrualPeriod().getYear(),
+    lmp.getDayOfYear(),
+    lmp.getYear(),
+    subscriberByRchId.getLastMenstrualPeriod().getDayOfYear() == lmp.getDayOfYear(),
+    subscriberByRchId.getLastMenstrualPeriod().getYear() == lmp.getYear());
kilkari/src/main/java/org/motechproject/nms/kilkari/service/impl/CsrServiceImpl.java (2)

31-31: Remove unused import.

The SettingsFacade import is not used anywhere in the code.

-import org.motechproject.server.config.SettingsFacade;

411-421: Enhance robustness of route number extraction.

The current implementation could be improved for better maintainability and error handling.

Consider this enhanced implementation:

-    private String extractRouteNumber(String serviceId) {
-        if (serviceId != null && serviceId.contains("Retryonroute")) {
-            LOGGER.info("inside this1");
-            int startIndex = serviceId.indexOf("Retryonroute") + "Retryonroute".length();
-            if (startIndex < serviceId.length()) {
-                LOGGER.info("inside this2");
-                return String.valueOf(serviceId.charAt(startIndex));
-            }
-        }
-        return "";
-    }
+    private static final String ROUTE_PREFIX = "Retryonroute";
+    
+    private String extractRouteNumber(String serviceId) {
+        if (serviceId == null || !serviceId.contains(ROUTE_PREFIX)) {
+            return "";
+        }
+        
+        try {
+            int startIndex = serviceId.indexOf(ROUTE_PREFIX) + ROUTE_PREFIX.length();
+            return startIndex < serviceId.length() ? 
+                   String.valueOf(serviceId.charAt(startIndex)) : 
+                   "";
+        } catch (IndexOutOfBoundsException e) {
+            LOGGER.warn("Failed to extract route number from serviceId: {}", serviceId, e);
+            return "";
+        }
+    }

Improvements:

  1. Added constant for the route prefix
  2. Added try-catch for better error handling
  3. Removed unnecessary debug logging
  4. Made the code more concise and readable
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 943974e and 0fea9b3.

📒 Files selected for processing (7)
  • imi/src/main/java/org/motechproject/nms/imi/domain/CallSummaryRecord.java (1 hunks)
  • kilkari/src/main/java/org/motechproject/nms/kilkari/dto/CallSummaryRecordDto.java (6 hunks)
  • kilkari/src/main/java/org/motechproject/nms/kilkari/service/SubscriptionService.java (1 hunks)
  • kilkari/src/main/java/org/motechproject/nms/kilkari/service/impl/CsrServiceImpl.java (10 hunks)
  • kilkari/src/main/java/org/motechproject/nms/kilkari/service/impl/SubscriberServiceImpl.java (3 hunks)
  • kilkari/src/main/java/org/motechproject/nms/kilkari/service/impl/SubscriptionServiceImpl.java (1 hunks)
  • props/src/main/java/org/motechproject/nms/props/domain/DayOfTheWeek.java (2 hunks)
🔇 Additional comments (9)
imi/src/main/java/org/motechproject/nms/imi/domain/CallSummaryRecord.java (1)

206-207: LGTM! Addition of serviceId parameter helps track service information.

The change to include serviceId in the DTO conversion is well-implemented and aligns with the goal of preventing duplicate records.

kilkari/src/main/java/org/motechproject/nms/kilkari/dto/CallSummaryRecordDto.java (3)

40-41: LGTM! Field declaration is well-placed.

The serviceId field is properly declared and positioned with other fields.


58-58: LGTM! Constructor updated correctly.

The constructor is properly updated to include the new serviceId parameter while maintaining backward compatibility.

Also applies to: 69-69


161-168: LGTM! Getter and setter follow conventions.

The getter and setter methods for serviceId are well-implemented following Java bean conventions.

props/src/main/java/org/motechproject/nms/props/domain/DayOfTheWeek.java (1)

4-5: LGTM!

The added imports from Joda Time are appropriate for the new timestamp parsing functionality.

kilkari/src/main/java/org/motechproject/nms/kilkari/service/SubscriptionService.java (1)

109-110: LGTM! Method signature is well-defined.

The new method signature clearly specifies its purpose to update call retry information with appropriate parameters.

kilkari/src/main/java/org/motechproject/nms/kilkari/service/impl/SubscriptionServiceImpl.java (1)

947-955: LGTM! Implementation is clean and handles null checks.

The implementation correctly updates the MSISDN for an existing call retry record and includes appropriate null checks.

kilkari/src/main/java/org/motechproject/nms/kilkari/service/impl/SubscriberServiceImpl.java (1)

749-757: LGTM! Call retry management is correctly implemented.

The code correctly manages call retry records based on date changes, either deleting or updating them as needed.

kilkari/src/main/java/org/motechproject/nms/kilkari/service/impl/CsrServiceImpl.java (1)

360-364: 🛠️ Refactor suggestion

Document the complex condition and consider edge cases.

The condition for handling RCH updates is complex and lacks documentation explaining the business logic. Additionally, there might be a race condition if multiple updates arrive simultaneously.

  1. Add documentation explaining the logic:
+    // Handle cases where RCH updates have changed the LMP date
+    // Route 1: Fresh calls after RCH update
+    // Route 2: Retry calls after RCH update
+    // Skip rescheduling if the day of week has changed due to LMP update
     if(callRetry == null && !csrDto.getWeekId().equals("w1_1") && 
        !subscription.getFirstMessageDayOfWeek().equals(DayOfTheWeek.getDayOfTheWeekFromTimestamp(csrDto.getTargetFileTimeStamp())) && 
        "1".equals(extractRouteNumber(csrDto.getServiceId()))) {
         LOGGER.info("inside fresh call condition after rch update");
     } else if(callRetry == null && !csrDto.getWeekId().equals("w1_1") && 
               "2".equals(extractRouteNumber(csrDto.getServiceId()))){
         LOGGER.info("inside retry call condition after rch update");
     }
  1. Consider adding synchronization or version control to handle concurrent updates:
@Version
private Long version;
✅ Verification successful

Complex condition is necessary, but needs documentation

The condition handles a specific business case for RCH updates affecting LMP dates, and the extensive debug logging already helps with monitoring. The suggested synchronization is unnecessary as this appears to be a business logic concern rather than a concurrency issue.

  1. Add documentation as suggested to explain the business logic:
+    // Handle cases where RCH updates have changed the LMP date:
+    // - Skip processing for first week messages (w1_1)
+    // - Verify day of week changes from LMP updates
+    // - Handle fresh calls (Route 1) and retry calls (Route 2) separately
🏁 Scripts executed

The following scripts were executed for the analysis:

Error: No shell scripts found


Script:

#!/bin/bash
# Search for similar RCH update handling patterns and version annotations
echo "=== Searching for RCH update handling patterns ==="
rg -l "rch update" --type java

echo -e "\n=== Checking for Version annotations and synchronization ==="
rg "@Version|@Lock" --type java

echo -e "\n=== Looking for related documentation ==="
fd README.md | xargs rg -i "rch|lmp|synchronization" || true

echo -e "\n=== Searching for similar complex conditions ==="
rg "getFirstMessageDayOfWeek|getDayOfTheWeekFromTimestamp" --type java -A 2 -B 2

Length of output: 4614

Comment on lines +43 to +51
public static DayOfTheWeek getDayOfTheWeekFromTimestamp(String timestamp) {
DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyyMMddHHmmss");

DateTime dateTime = formatter.parseDateTime(timestamp);

int dayOfWeek = dateTime.getDayOfWeek(); // 1 = Monday, 7 = Sunday

return DayOfTheWeek.fromInt(dayOfWeek);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling and documentation.

The method needs the following improvements:

  1. Add null check and proper exception handling for parsing errors
  2. Add javadoc to document the expected timestamp format
  3. Make the formatter a static final field for better performance

Here's the suggested implementation:

+    private static final DateTimeFormatter TIMESTAMP_FORMATTER = 
+            DateTimeFormat.forPattern("yyyyMMddHHmmss");
+
+    /**
+     * Converts a timestamp string to DayOfTheWeek.
+     *
+     * @param timestamp String in format "yyyyMMddHHmmss"
+     * @return DayOfTheWeek corresponding to the timestamp
+     * @throws IllegalArgumentException if timestamp is null or in invalid format
+     */
     public static DayOfTheWeek getDayOfTheWeekFromTimestamp(String timestamp) {
-        DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyyMMddHHmmss");
+        if (timestamp == null) {
+            throw new IllegalArgumentException("Timestamp cannot be null");
+        }
+        try {
+            DateTime dateTime = TIMESTAMP_FORMATTER.parseDateTime(timestamp);
+            return DayOfTheWeek.fromInt(dateTime.getDayOfWeek());
+        } catch (IllegalArgumentException e) {
+            throw new IllegalArgumentException(
+                "Invalid timestamp format. Expected: yyyyMMddHHmmss", e);
+        }
-        DateTime dateTime = formatter.parseDateTime(timestamp);
-
-        int dayOfWeek = dateTime.getDayOfWeek(); // 1 = Monday, 7 = Sunday
-
-        return DayOfTheWeek.fromInt(dayOfWeek);
     }
📝 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
public static DayOfTheWeek getDayOfTheWeekFromTimestamp(String timestamp) {
DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyyMMddHHmmss");
DateTime dateTime = formatter.parseDateTime(timestamp);
int dayOfWeek = dateTime.getDayOfWeek(); // 1 = Monday, 7 = Sunday
return DayOfTheWeek.fromInt(dayOfWeek);
}
private static final DateTimeFormatter TIMESTAMP_FORMATTER =
DateTimeFormat.forPattern("yyyyMMddHHmmss");
/**
* Converts a timestamp string to DayOfTheWeek.
*
* @param timestamp String in format "yyyyMMddHHmmss"
* @return DayOfTheWeek corresponding to the timestamp
* @throws IllegalArgumentException if timestamp is null or in invalid format
*/
public static DayOfTheWeek getDayOfTheWeekFromTimestamp(String timestamp) {
if (timestamp == null) {
throw new IllegalArgumentException("Timestamp cannot be null");
}
try {
DateTime dateTime = TIMESTAMP_FORMATTER.parseDateTime(timestamp);
return DayOfTheWeek.fromInt(dateTime.getDayOfWeek());
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(
"Invalid timestamp format. Expected: yyyyMMddHHmmss", e);
}
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🔭 Outside diff range comments (1)
kilkari/src/main/java/org/motechproject/nms/kilkari/service/impl/SubscriberServiceImpl.java (1)

436-436: Remove duplicate deleteCallRetry call.

This line duplicates the deleteCallRetry logic already handled in lines 420-428. It will delete retry records even when only MSISDN changed (not LMP), which contradicts the intended logic.

-                if(subscription != null){subscriptionService.deleteCallRetry(subscription.getSubscriptionId());}
🧹 Nitpick comments (3)
api/src/main/java/org/motechproject/nms/api/web/KilkariController.java (1)

402-411: Consider improving CSV parsing robustness.

The current implementation uses simple comma splitting which won't handle quoted values containing commas or other CSV edge cases.

Consider using a proper CSV parsing library like Apache Commons CSV or OpenCSV for more robust parsing:

-    private List<String[]> readFileContent(BufferedReader reader) throws IOException {
-        List<String[]> records = new ArrayList<>();
-        String line;
-        while ((line = reader.readLine()) != null) {
-            if (!line.trim().isEmpty()) {
-                records.add(line.split(","));
-            }
-        }
-        return records;
-    }
+    private List<String[]> readFileContent(BufferedReader reader) throws IOException {
+        // Consider using Apache Commons CSV for robust parsing
+        List<String[]> records = new ArrayList<>();
+        String line;
+        while ((line = reader.readLine()) != null) {
+            if (!line.trim().isEmpty()) {
+                // TODO: Handle quoted values, escaped commas, etc.
+                records.add(line.split(",", -1)); // -1 to preserve empty trailing fields
+            }
+        }
+        return records;
+    }
kilkari/src/main/java/org/motechproject/nms/kilkari/service/impl/SubscriberServiceImpl.java (2)

413-422: Remove or adjust debug logging statements.

These info-level logs appear to be temporary debugging statements. They should either be removed or changed to debug level to avoid cluttering production logs.

-                    LOGGER.info("we are inside not null condition");
-                    LOGGER.info("1st value: {}",subscriberByRchId.getLastMenstrualPeriod().getDayOfYear());
-                    LOGGER.info("second value: {}",lmp.getDayOfYear());
-                    LOGGER.info("third value: {}",subscriberByRchId.getLastMenstrualPeriod().getYear());
-                    LOGGER.info("second value: {}",lmp.getYear());
-                    LOGGER.info("this is the first case: {}",(subscriberByRchId.getLastMenstrualPeriod().getDayOfYear() == lmp.getDayOfYear()));
-                    LOGGER.info("this is the second case2: {}",(subscriberByRchId.getLastMenstrualPeriod().getYear() == lmp.getYear()));
+                    LOGGER.debug("Checking LMP date change - old: {}, new: {}", 
+                        subscriberByRchId.getLastMenstrualPeriod(), lmp);

1235-1243: Consider more specific exception handling.

The catch block catches all exceptions, which might mask specific issues. Consider catching and handling specific exceptions differently.

-            } catch (Exception e) {
-                failureMessages.add("Failed to process msisdn " + record[0] + ": " + e.getMessage());
+            } catch (IllegalArgumentException e) {
+                failureMessages.add("Invalid data for msisdn " + record[0] + ": " + e.getMessage());
+            } catch (IllegalStateException e) {
+                failureMessages.add("Failed to deactivate msisdn " + record[0] + ": " + e.getMessage());
+            } catch (Exception e) {
+                LOGGER.error("Unexpected error processing msisdn " + record[0], e);
+                failureMessages.add("Unexpected error for msisdn " + record[0] + ": " + e.getMessage());
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 088a15b and 0f0a619.

📒 Files selected for processing (7)
  • api/src/main/java/org/motechproject/nms/api/web/BaseController.java (1 hunks)
  • api/src/main/java/org/motechproject/nms/api/web/KilkariController.java (4 hunks)
  • imi/src/main/resources/imi.properties (1 hunks)
  • kilkari/src/main/java/org/motechproject/nms/kilkari/service/SubscriberService.java (2 hunks)
  • kilkari/src/main/java/org/motechproject/nms/kilkari/service/impl/SubscriberServiceImpl.java (7 hunks)
  • rch/src/main/java/org/motechproject/nms/rch/service/impl/RchWebServiceFacadeImpl.java (1 hunks)
  • rch/src/main/resources/rch.properties (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • imi/src/main/resources/imi.properties
  • rch/src/main/resources/rch.properties
  • rch/src/main/java/org/motechproject/nms/rch/service/impl/RchWebServiceFacadeImpl.java
🔇 Additional comments (6)
api/src/main/java/org/motechproject/nms/api/web/BaseController.java (1)

390-390: Good security improvement!

Replacing the actual exception message with a generic error message prevents potential information leakage about internal implementation details.

kilkari/src/main/java/org/motechproject/nms/kilkari/service/SubscriberService.java (1)

17-17: LGTM! Well-designed method signature for bulk processing.

The method signature is well-designed with thread-safe AtomicInteger for success counting and a list for collecting failure messages, suitable for concurrent processing.

Also applies to: 121-121

kilkari/src/main/java/org/motechproject/nms/kilkari/service/impl/SubscriberServiceImpl.java (4)

412-428: Critical fix for preventing duplicate OBD records!

This logic correctly handles call retry records when LMP dates change, which addresses the core issue mentioned in the PR objective. When the LMP date changes, existing call retry records are deleted to prevent duplicate OBD entries. When only the MSISDN changes, the retry records are updated.


752-759: Good implementation of call retry handling for child records.

The logic correctly handles call retry records based on DOB changes, consistent with the mother subscriber handling.


1011-1018: Excellent logic to prevent conflicting subscriptions!

The checks for active child subscriptions before reactivating mother subscriptions prevent data inconsistencies and potential duplicate records. This is a crucial fix for maintaining data integrity.

Also applies to: 1036-1046


1133-1133: Good addition of transactional boundary.

Adding @Transactional ensures atomicity for the entire deactivation process, preventing partial updates in case of failures.

Comment on lines 368 to 390
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
List<Future<Void>> futures = new ArrayList<>();

for (int i = 0; i < threadCount; i++) {
int start = i * chunkSize;
int end = Math.min(start + chunkSize, records.size());
if (start >= records.size()) {break;}
List<String[]> chunk = records.subList(start, end);

Future<Void> future = executor.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
subscriberService.processChunkOfDeactivation(chunk, successCount, failureMessages);
return null;
}
});
futures.add(future);
}

for (Future<Void> future : futures) {
future.get();
}
executor.shutdown();
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Ensure proper executor shutdown and error handling.

The executor service needs better resource management and error handling:

  1. The executor should be shut down in a finally block or use try-with-resources
  2. Add timeout for executor termination

Apply this diff to improve executor management:

-            ExecutorService executor = Executors.newFixedThreadPool(threadCount);
-            List<Future<Void>> futures = new ArrayList<>();
-
-            for (int i = 0; i < threadCount; i++) {
-                int start = i * chunkSize;
-                int end = Math.min(start + chunkSize, records.size());
-                if (start >= records.size()) {break;}
-                List<String[]> chunk = records.subList(start, end);
-
-                Future<Void> future = executor.submit(new Callable<Void>() {
-                    @Override
-                    public Void call() throws Exception {
-                        subscriberService.processChunkOfDeactivation(chunk, successCount, failureMessages);
-                        return null;
-                    }
-                });
-                futures.add(future);
-            }
-
-            for (Future<Void> future : futures) {
-                future.get();
-            }
-            executor.shutdown();
+            ExecutorService executor = Executors.newFixedThreadPool(threadCount);
+            try {
+                List<Future<Void>> futures = new ArrayList<>();
+
+                for (int i = 0; i < threadCount; i++) {
+                    int start = i * chunkSize;
+                    int end = Math.min(start + chunkSize, records.size());
+                    if (start >= records.size()) {break;}
+                    List<String[]> chunk = records.subList(start, end);
+
+                    Future<Void> future = executor.submit(new Callable<Void>() {
+                        @Override
+                        public Void call() throws Exception {
+                            subscriberService.processChunkOfDeactivation(chunk, successCount, failureMessages);
+                            return null;
+                        }
+                    });
+                    futures.add(future);
+                }
+
+                for (Future<Void> future : futures) {
+                    future.get();
+                }
+            } finally {
+                executor.shutdown();
+                try {
+                    if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
+                        executor.shutdownNow();
+                    }
+                } catch (InterruptedException e) {
+                    executor.shutdownNow();
+                    Thread.currentThread().interrupt();
+                }
+            }
📝 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
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
List<Future<Void>> futures = new ArrayList<>();
for (int i = 0; i < threadCount; i++) {
int start = i * chunkSize;
int end = Math.min(start + chunkSize, records.size());
if (start >= records.size()) {break;}
List<String[]> chunk = records.subList(start, end);
Future<Void> future = executor.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
subscriberService.processChunkOfDeactivation(chunk, successCount, failureMessages);
return null;
}
});
futures.add(future);
}
for (Future<Void> future : futures) {
future.get();
}
executor.shutdown();
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
try {
List<Future<Void>> futures = new ArrayList<>();
for (int i = 0; i < threadCount; i++) {
int start = i * chunkSize;
int end = Math.min(start + chunkSize, records.size());
if (start >= records.size()) { break; }
List<String[]> chunk = records.subList(start, end);
Future<Void> future = executor.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
subscriberService.processChunkOfDeactivation(chunk, successCount, failureMessages);
return null;
}
});
futures.add(future);
}
for (Future<Void> future : futures) {
future.get();
}
} finally {
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
🤖 Prompt for AI Agents
In api/src/main/java/org/motechproject/nms/api/web/KilkariController.java around
lines 368 to 390, the ExecutorService is not properly shut down and lacks error
handling. Refactor the code to shut down the executor in a finally block or use
try-with-resources to ensure it always shuts down. Add a timeout to await
termination after shutdown to avoid indefinite blocking. Also, handle
InterruptedException and ExecutionException when calling future.get() to
properly manage errors during task execution.

@Kantas2601 Kantas2601 force-pushed the Duplicates-in-OBD-file-fix branch from 0b09fc3 to 880faef Compare June 26, 2025 08:14
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