From fd4f8c41bfe25a3bdb83ad8849d47c50a4324fef Mon Sep 17 00:00:00 2001 From: Hari-stackroute <40484996+Hari-stackroute@users.noreply.github.com> Date: Thu, 31 Oct 2019 17:57:48 +0530 Subject: [PATCH] SC 1397 - merged to release-2.4.0 (#811) * Issue #SB-14333 feat: changes for reset url * Issue #SB-14333 feat: changes for reset url * Issue #SB-14223 instead of normal updates, now we can update with batch and changes regarding while adding and getting json data * Issue#SC-1258 HEADER IS BEEN SET TO THE POST REQUEST * Issue#SC-1293 added courseId and batchId in cert add request * Issue#SC-1293 removed extra batchId field * Issue#SC-1293 removed extra batchId field * Issue #SB-14377 feat: sending email/sms notification after user account merge * Issue #SB-14377 feat: adding firstname in email request * Issue #SB-14377 feat: chnages for get user by id call * Issue #SB-14372 Telemetry code changes while adding certificate, merging certificate and merging user * Issue #SB-14372 bug: if fromaccount deosn't contains certificates, response is not triggering and hence socekt timeout is happening in this scenario * Issue#SC-14483 number of instances increased for UserManagement and other utility actors * background actor instances increased * Issue#SC-1308 added support for recoveryEmail and recoveryPhone * Issue#SC-1308 added masked recoveryEmail and recoveryPhone * Issue#SC-1308 added logs * Issue #SB-14537 feat:checking user belongs to state and validating * Issue #SB-14537 user json change * Issue#SC-1308 METHOD NAME CHANGED * Issue#SC-1308 METHOD NAME CHANGED * Issue #SB-14537 review changes * Issue #SB-14537 added cache capability, checking root-org-channel value is present in cache else get and set in cache * Issue#SC-1308 method signature changed * Issue#SC-1308 init method initialized * Issue#SC-1308 added conditions for recovery email * Issue#SC-1308 dual initiliazation removed * Issue#SC-1308 pr comments resolved * Issuse #SB-00 feat:changes for submodules * Issue #SB-15437 code review changes * Issue #SB-00 feat: creating new branch from release-2.3.0 * Issue#SC-1310 added builder to validate request * Validation started * Issue#SC-1308 nullify recoveryEmail/phone if present in request and is blank * Issue#SC-1310 headers validated * Issue SB-14537 adding statevalidation for non-custodian and flags to decimal value * Issue #SB-14537 adding boolean flags to es * Issue #SB-14537 removed boolean flags * removing course batch count scheduler from learner-service * removing invalid report metric job from scheduler * removing commented call * removing unused course publish job * Issue #SB-14630 fix: changes for email subject incase of reset password * Issue #SB-14630 fix: changes for matching logic * Issue #SB-14630 fix: changes for method call order * Issue#SC-1310 validation started * Issue#SC-1310 user record inserted to cassandra with data encryption * Issue#SC-1310 rows validated * Issue#SC-1310 dispatcher added for list * Issue#SC-1310 added csverror dispatcher * Issue#SC-1310 added csverror dispatcher * Issue#SC-1310 changed error message for list diaptcher * Issue#SC-1310 added timestamp for encrption * Issue#SC-1310 reading csv once only * Issue#SC-1310 pr comments resolved * Issue #SB-14537 adding constants * Issue#SC-1310 discussed changes made * Issue#SC-1310 ADDED JAVA DOC FOR VALIDATOR CLASS * Issue#SC-1310 added comparator for rows * Issue#SC-1310 unused import reoved * Issue#SC-1310 method renamed * Issue#SC-1310 blank csv check made * Issue#SC-1310 added channel from the user token * Issue#SC-1310 ADDED ROOT ORGANISATION ID IN THE RECORD * Issue#SC-1310 changed retry count to 2 * Issue #SC-1324 feat:fixed compilation issue of UserMergeActor class * Issue #SB-14665 feat:removing code to nullify username while merge * Issue #SB-14537 created userflag util for enum value conversion * Issue #SB-14537 code-review changes * Issue#SC-1323 record inserted to shadow db * Issue#SC-1323 USER STARTED UPDATING * Issue#SC-1323 STATUS MESSAGES ADDED * Issue #SB-14665 feat: reverting back the code * Issue#SC-1323 stage 2 completed * Issue #14537 As of now ignoring test cases for making build success * Issue #14537 As of now ignoring test cases for making build success(removing from base class) * Issue #14537 As of now ignoring UserFrameWork test for making build success * Stage3 processing * Stage3 completed * Stage3 completed * issue# Sb-14608 : sent notification after migrating user * Issue #SB-14537 replacing with constants * Issue #SB-14537 fix for test case failures * Issue #SB-14537 removed unnecessary code * issue#SB-14608: review changes, added contant for sunbird_account_merge_subject * Issue#SC-1323 used added to user external id tabe * added process id in response * Issue#SC-1323 if user is claimed then only add userId * Issue #SB-14537 updating exisiting user are failing, fixed * Issue#SC-1323 scenerio tested * Issue #SB-14537 tenantmigration changes for stateValidation field * Issue #SB-14537 comment added * Issue#SC-1323 added scheduler job * Issue SB-14537 usertype not reflecting since request doesn't contain the value * Issue#SB-14784 splitted name in fuzzy search * Issue #SB-14729 feat: exception msg change * Issue#SC-1323 added cronjob for trigerring shadow user migration * Issue#SC-1323 added userIds when esUserMap is greater than 1 * Issue#SC-1323 pr comments resolved * Issue#SC-1323 CACHING OF ORGID IS DONE ON BASED OF CHANNEL * Issue#SC-1323 added LOCAL CACHE FOR ORGEXTERNALID as well * Issue#SC-1323 added LOCAL CACHE FOR ORGEXTERNALID as well * Issue#SC-1323 EXTERNALIDMISMATCH TO ORGEXTERNALIDMISMATCH and checking combination to channel and orgExternaid in set * Issue#SC-1323 EXTERNALIDMISMATCH TO ORGEXTERNALIDMISMATCH and checking combination to channel and orgExternaid in set * Issue#SC-1323 EXTERNALIDMISMATCH TO ORGEXTERNALIDMISMATCH and checking combination to channel and orgExternaid in set * Issue#SC-1323 orgexternalid to lower case and more logs added * Issue#SC-1323 orgexternalid to lower case and more logs added * Issue#SC-1323 added logs * Issue#SC-1323 custodian org id made non static * Issue#SC-1323 LOGS ADDED * Issue#SC-1323 LOGS ADDED * Issue#SC-1323 logs added * Issue#SC-1323 ADDED LOGS * Issue#SC-1323 ADDED LOGS * Issue#SC-1323 ADDED LOGS * Issue#SC-1323 Added Logs * #Issue#SC-1323 logs added with processId * Issue#SC-1323 updating processId * Issue #SB-14537 removed boolean flag parameter from UserFlagEnum * Issue #SB-14537 in getFlagValue method flagEnabled value no need to check with default value * Issue #SC-1333 feat: merge release-2.3.5 to release-2.4.0 * Issue#SC-1323 updated processid * Issue#SC-1323 remove organisations when ext org id provided in null * Issue#SC-1323 ORG DELETED * Issue#SC-1323 ORG DELETED * Issue#SC-1323 shadowprocessor object to local varibale * Issue #SC-1335 feat:masked email and masked phone issue fix * Issue#SC-1323 Flagvalue is been added to user data * Issue#SC-1323 ADDED FLAGSVALUE * Issue#SC-1323 added flasg * Issue#SC-1323 started processing user processIds wise * Issue#SC-1323 started processing user processIds wise * Issue#SC-1323 Async Call to update methods * Issue#SC-1323 Cassandra Async exectured * Issue#SC-1323 EXECUTOR THREADS REDUCED TO 5 * Issue#SC-1323 EXECUTOR THREADS REDUCED TO 5 * Revert "Issue#SC-1323 Async Call to update methods" * Issue#SC-1323 ADDED LOGS * Issue#SC-1323 ADDED LOGS TO DIFFRENTIATE BUILD * Issue#SC-1323 ADDED LOGS TO DIFFRENTIATE BUILD * Issue#SC-1323 ADDED SENNDE LOG * Issue#SC-1323 claimed status changed * #Issue#SC-1323 CHANGED * Issue#SC-1323 added logs * Issue#SC-1323 encryption issue fixed * Issue#SC-1323 started picking all unclaimed user in batches * Issue#SC-1339 teleemtry added * Issue#SC-1339 added telemetrycontext in bulk upload process table * Issue#SC-1339 added telemetry while claiming user * Issue#SC-1339 changed actor type from user to system with uuid * Issue#SC-1339 email and phone to lower case * Issue#SC-1339 email and phone to lower case * issue #SB-14977 fix: changes for travis build failure due to open jdk * Update .travis.yml * Issue#SC-1332 fixed headers validation * Issue#SB-15047 pr comments resolved * Issue #SB-15085 checking emailverified and phoneverified for existing users * Issue #SB-13773 fix: changes for amking syn call for user update and sunstracting flagsvalue as well , if present. * Issue #SB-15085 condition changed for existing users * Issue #SB-15085 moved to separate function * SB-15098 (#776) * Issue#SB-15098 ADDED NAME REGEX MATCHES * Issue#SB-15098 name trimmed before setting to object * SB-15106 (#777) * Issue#SB-15106 CONFIGURED DUPLICATE CHECK FOR EMAIL AND PHONE IN CSV * Issue#SB-15106 removed configuration permanently disabled the duplicate email and phone check * Issue #SB-14999 code changes for kafka producer issue * Issue #SB-14999 added log to see code is reflected or not. * Issue #00 feat: adding circleci config file (#782) * Issue#SB-15220 added recoveryEmail and recoveryPhone in otp generate request (#783) * Issue#SB-15220 enhance OTP functionality to send otp to Recovery email and phone (#784) * Issue#SC-1382 deleting OTP when its verified (#786) * Issue#SB-15342 removed special charater check (#788) * SC-15342 (#789) * Issue#SB-15342 removed special charater check * Issue#SB-15342 username can' contains . at begining and special characters * issue #SB-15346 fix: in forgot password supporting multi language for fuzzy match * issue #SB-15346 fix: in forgot password supporting multi language for fuzzy match --- .circleci/config.yml | 20 + .travis.yml | 3 +- .../java/org/sunbird/bean/ClaimStatus.java | 26 + .../java/org/sunbird/bean/MigrationUser.java | 88 +++ .../java/org/sunbird/bean/ShadowUser.java | 218 ++++++++ .../org/sunbird/bean/ShadowUserUpload.java | 141 +++++ .../sunbird/common/ShadowUserProcessor.java | 500 ++++++++++++++++++ .../quartz/scheduler/SchedulerManager.java | 40 +- .../ShadowUserMigrationScheduler.java | 378 +++++++++++++ .../main/java/org/sunbird/error/CsvError.java | 24 + .../org/sunbird/error/CsvErrorDispatcher.java | 38 ++ .../org/sunbird/error/CsvRowErrorDetails.java | 50 ++ .../java/org/sunbird/error/ErrorEnum.java | 15 + .../org/sunbird/error/IErrorDispatcher.java | 16 + .../sunbird/error/ListErrorDispatcher.java | 39 ++ .../java/org/sunbird/error/RowComparator.java | 10 + .../error/factory/ErrorDispatcherFactory.java | 45 ++ .../bulkupload/BaseBulkUploadActor.java | 2 - .../bulkupload/UserBulkMigrationActor.java | 368 +++++++++++++ .../UserBulkUploadBackgroundJobActor.java | 58 +- .../bulkupload/model/BulkMigrationUser.java | 283 ++++++++++ .../sunbird/learner/actors/otp/OTPActor.java | 288 +++++----- .../learner/actors/otp/SendOTPActor.java | 136 ++--- .../learner/actors/otp/dao/OTPDao.java | 7 + .../actors/otp/dao/impl/OTPDaoImpl.java | 11 + .../actors/otp/service/OTPService.java | 4 + .../learner/actors/search/FuzzyMatcher.java | 140 +++-- .../actors/search/FuzzySearchManager.java | 159 +++--- .../actors/search/SearchHandlerActor.java | 1 + .../sunbird/learner/util/UserFlagEnum.java | 32 ++ .../sunbird/learner/util/UserFlagUtil.java | 57 ++ .../org/sunbird/learner/util/UserUtility.java | 59 ++- .../java/org/sunbird/learner/util/Util.java | 13 +- .../java/org/sunbird/models/user/User.java | 47 +- .../UserBulkMigrationRequestValidator.java | 184 +++++++ .../user/actors/IdentifierFreeUpActor.java | 197 ++++--- .../user/actors/TenantMigrationActor.java | 66 ++- .../user/actors/UserManagementActor.java | 167 +++++- .../sunbird/user/actors/UserMergeActor.java | 16 +- .../org/sunbird/user/service/UserService.java | 3 +- .../user/service/impl/UserServiceImpl.java | 18 +- .../org/sunbird/user/UserFrameworkTest.java | 1 + .../sunbird/user/UserManagementActorTest.java | 17 +- .../user/UserManagementActorTestBase.java | 9 + .../user/UserProfileReadActorTest.java | 1 + .../org/sunbird/middleware/Application.java | 4 +- service/src/main/resources/application.conf | 8 +- 47 files changed, 3485 insertions(+), 522 deletions(-) create mode 100644 .circleci/config.yml create mode 100644 actors/common/src/main/java/org/sunbird/bean/ClaimStatus.java create mode 100644 actors/common/src/main/java/org/sunbird/bean/MigrationUser.java create mode 100644 actors/common/src/main/java/org/sunbird/bean/ShadowUser.java create mode 100644 actors/common/src/main/java/org/sunbird/bean/ShadowUserUpload.java create mode 100644 actors/common/src/main/java/org/sunbird/common/ShadowUserProcessor.java create mode 100644 actors/common/src/main/java/org/sunbird/common/quartz/scheduler/ShadowUserMigrationScheduler.java create mode 100644 actors/common/src/main/java/org/sunbird/error/CsvError.java create mode 100644 actors/common/src/main/java/org/sunbird/error/CsvErrorDispatcher.java create mode 100644 actors/common/src/main/java/org/sunbird/error/CsvRowErrorDetails.java create mode 100644 actors/common/src/main/java/org/sunbird/error/ErrorEnum.java create mode 100644 actors/common/src/main/java/org/sunbird/error/IErrorDispatcher.java create mode 100644 actors/common/src/main/java/org/sunbird/error/ListErrorDispatcher.java create mode 100644 actors/common/src/main/java/org/sunbird/error/RowComparator.java create mode 100644 actors/common/src/main/java/org/sunbird/error/factory/ErrorDispatcherFactory.java create mode 100644 actors/common/src/main/java/org/sunbird/learner/actors/bulkupload/UserBulkMigrationActor.java create mode 100644 actors/common/src/main/java/org/sunbird/learner/actors/bulkupload/model/BulkMigrationUser.java create mode 100644 actors/common/src/main/java/org/sunbird/learner/util/UserFlagEnum.java create mode 100644 actors/common/src/main/java/org/sunbird/learner/util/UserFlagUtil.java create mode 100644 actors/common/src/main/java/org/sunbird/validator/user/UserBulkMigrationRequestValidator.java diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 000000000..92a19b2a9 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,20 @@ +version: 2.1 +jobs: + build: + machine: true + steps: + - checkout + - run: git submodule update --init + - run: git submodule update --init --recursive --remote + - restore_cache: + key: lms-dependency-cache-{{ checksum "pom.xml" }} + - run: mvn clean install + - save_cache: + key: lms-dependency-cache-{{ checksum "pom.xml" }} + paths: ~/.m2 + +workflows: + version: 2.1 + workflow: + jobs: + - build diff --git a/.travis.yml b/.travis.yml index 2d5a47051..c374c4889 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,10 @@ language: java jdk: openjdk8 install: echo "Over-riding default script for travis-ci" +dist: trusty script: - jdk_switcher use openjdk8 - - mvn clean package '-Dtest=!%regex[io.opensaber.registry.client.*]' -DfailIfNoTests=false + - mvn clean package -q '-Dtest=!%regex[io.opensaber.registry.client.*]' -DfailIfNoTests=false - bash <(curl -s https://codecov.io/bash) cache: directories: diff --git a/actors/common/src/main/java/org/sunbird/bean/ClaimStatus.java b/actors/common/src/main/java/org/sunbird/bean/ClaimStatus.java new file mode 100644 index 000000000..aaec32a8b --- /dev/null +++ b/actors/common/src/main/java/org/sunbird/bean/ClaimStatus.java @@ -0,0 +1,26 @@ +package org.sunbird.bean; + + +/** + * this class will have user action performed on the shadow user record. + * @author anmolgupta + * + */ +public enum ClaimStatus { + + + CLAIMED(1), + UNCLAIMED(0), + REJECTED(2), + FAILED(3), + MULTIMATCH(4), + ORGEXTERNALIDMISMATCH(5); + + private int value; + ClaimStatus(int value) { + this.value = value; + } + public int getValue() { + return value; + } +} diff --git a/actors/common/src/main/java/org/sunbird/bean/MigrationUser.java b/actors/common/src/main/java/org/sunbird/bean/MigrationUser.java new file mode 100644 index 000000000..5f176712b --- /dev/null +++ b/actors/common/src/main/java/org/sunbird/bean/MigrationUser.java @@ -0,0 +1,88 @@ +package org.sunbird.bean; + + +import java.io.Serializable; + +public class MigrationUser implements Serializable { + + private String email; + private String phone; + private String name; + private String userExternalId; + private String orgExternalId; + private String channel; + private String inputStatus; + + public void setEmail(String email) { + this.email = email; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public void setName(String name) { + this.name = name; + } + + public void setUserExternalId(String userExternalId) { + this.userExternalId = userExternalId; + } + + public void setOrgExternalId(String orgExternalId) { + this.orgExternalId = orgExternalId; + } + + public void setChannel(String state) { + this.channel = state; + } + + public void setInputStatus(String inputStatus) { + this.inputStatus = inputStatus; + } + + public MigrationUser() { + } + + + public String getEmail() { + return email; + } + + public String getPhone() { + return phone; + } + + public String getName() { + return name; + } + + public String getUserExternalId() { + return userExternalId; + } + + public String getOrgExternalId() { + return orgExternalId; + } + + public String getChannel() { + return channel; + } + + public String getInputStatus() { + return inputStatus; + } + + @Override + public String toString() { + return "MigrationUser{" + + "email='" + email + '\'' + + ", phone='" + phone + '\'' + + ", name='" + name + '\'' + + ", userExternalId='" + userExternalId + '\'' + + ", orgExternalId='" + orgExternalId + '\'' + + ", state='" + channel + '\'' + + ", inputStatus=" + inputStatus + + '}'; + } +} diff --git a/actors/common/src/main/java/org/sunbird/bean/ShadowUser.java b/actors/common/src/main/java/org/sunbird/bean/ShadowUser.java new file mode 100644 index 000000000..7ff8a7d68 --- /dev/null +++ b/actors/common/src/main/java/org/sunbird/bean/ShadowUser.java @@ -0,0 +1,218 @@ +package org.sunbird.bean; + +import java.sql.Timestamp; +import java.util.List; + + +/** + * this is POJO class for the shadow user in the shadow_user table + * @author anmolgupta + */ +public class ShadowUser { + private Timestamp claimedOn; + private int claimStatus; + private Timestamp createdOn; + private String email; + private String name; + private String orgExtId; + private String phone; + private String processId; + private Timestamp updatedOn; + private String userExtId; + private String userId; + private int userStatus; + private ListuserIds; + private String channel; + private String addedBy; + + public ShadowUser() { + } + + public ShadowUser(ShadowUserBuilder shadowUserBuilder) { + this.claimedOn = shadowUserBuilder.claimedOn; + this.claimStatus = shadowUserBuilder.claimStatus; + this.createdOn = shadowUserBuilder.createdOn; + this.email = shadowUserBuilder.email; + this.name = shadowUserBuilder.name; + this.orgExtId = shadowUserBuilder.orgExtId; + this.phone = shadowUserBuilder.phone; + this.processId = shadowUserBuilder.processId; + this.updatedOn = shadowUserBuilder.updatedOn; + this.userExtId = shadowUserBuilder.userExtId; + this.userId = shadowUserBuilder.userId; + this.userStatus = shadowUserBuilder.userStatus; + this.channel=shadowUserBuilder.channel; + this.addedBy=shadowUserBuilder.addedBy; + this.userIds=shadowUserBuilder.userIds; + } + public Timestamp getClaimedOn() { + return claimedOn; + } + + public String getAddedBy() { + return addedBy; + } + + + public List getUserIds() { + return userIds; + } + + public int getClaimStatus() { + return claimStatus; + } + + public Timestamp getCreatedOn() { + return createdOn; + } + + public String getEmail() { + return email; + } + + public String getName() { + return name; + } + + public String getOrgExtId() { + return orgExtId; + } + + public String getPhone() { + return phone; + } + + public String getProcessId() { + return processId; + } + + public Timestamp getUpdatedOn() { + return updatedOn; + } + + public String getUserExtId() { + return userExtId; + } + + public String getUserId() { + return userId; + } + + public int getUserStatus() { + return userStatus; + } + + public String getChannel() { + return channel; + } + + public static class ShadowUserBuilder{ + + private Timestamp claimedOn; + private int claimStatus; + private Timestamp createdOn; + private String email; + private String name; + private String orgExtId; + private String phone; + private String processId; + private Timestamp updatedOn; + private String userExtId; + private String userId; + private int userStatus; + private String channel; + private String addedBy; + private ListuserIds; + + public ShadowUserBuilder setUserIds(List userIds) { + this.userIds = userIds; + return this; + } + + public ShadowUserBuilder setAddedBy(String addedBy) { + this.addedBy = addedBy; + return this; + } + + public ShadowUserBuilder setChannel(String channel) { + this.channel = channel; + return this; + } + + public ShadowUserBuilder setClaimedOn(Timestamp claimedOn) { + this.claimedOn = claimedOn; + return this; + + } + + public ShadowUserBuilder setClaimStatus(int claimStatus) { + this.claimStatus = claimStatus; + return this; + + } + + public ShadowUserBuilder setCreatedOn(Timestamp createdOn) { + this.createdOn = createdOn; + return this; + + } + + public ShadowUserBuilder setEmail(String email) { + this.email = email; + return this; + + } + + public ShadowUserBuilder setName(String name) { + this.name = name; + return this; + + } + + public ShadowUserBuilder setOrgExtId(String orgExternalId) { + this.orgExtId = orgExternalId; + return this; + + } + + public ShadowUserBuilder setPhone(String phone) { + this.phone = phone; + return this; + + } + + public ShadowUserBuilder setProcessId(String processId) { + this.processId = processId; + return this; + + } + + public ShadowUserBuilder setUpdatedOn(Timestamp updatedOn) { + this.updatedOn = updatedOn; + return this; + + } + + public ShadowUserBuilder setUserExtId(String userExtId) { + this.userExtId = userExtId; + return this; + + } + + public ShadowUserBuilder setUserId(String userId) { + this.userId = userId; + return this; + + } + + public ShadowUserBuilder setUserStatus(int userStatus) { + this.userStatus = userStatus; + return this; + } + + public ShadowUser build(){ + ShadowUser shadowUser=new ShadowUser(this); + return shadowUser; + } + } +} diff --git a/actors/common/src/main/java/org/sunbird/bean/ShadowUserUpload.java b/actors/common/src/main/java/org/sunbird/bean/ShadowUserUpload.java new file mode 100644 index 000000000..7fa3c95e2 --- /dev/null +++ b/actors/common/src/main/java/org/sunbird/bean/ShadowUserUpload.java @@ -0,0 +1,141 @@ +package org.sunbird.bean; + +import org.sunbird.validator.user.UserBulkMigrationRequestValidator; + +import java.util.Arrays; +import java.util.List; + +public class ShadowUserUpload { + + private String fileSize; + private Listheaders; + private ListmappedHeaders; + private byte[] fileData; + private ListmandatoryFields; + private ListsupportedFields; + private String processId; + private Listvalues; + + private ShadowUserUpload(ShadowUserUploadBuilder migrationBuilder) { + this.fileSize = migrationBuilder.fileSize; + this.headers = migrationBuilder.headers; + this.fileData = migrationBuilder.fileData; + this.mandatoryFields=migrationBuilder.mandatoryFields; + this.supportedFields=migrationBuilder.supportedFields; + this.processId=migrationBuilder.processId; + this.values=migrationBuilder.values; + this.mappedHeaders=migrationBuilder.mappedHeaders; + + } + public String getFileSize() { + return fileSize; + } + + public List getHeaders() { + return headers; + } + + public byte[] getFileData() { + return fileData; + } + + public List getMandatoryFields() { + return mandatoryFields; + } + + public List getSupportedFields() { + return supportedFields; + } + + public List getValues() { + return values; + } + public List getMappedHeaders() { + return mappedHeaders; + } + + + @Override + public String toString() { + return "ShadowUserUpload{" + + "fileSize='" + fileSize + '\'' + + ", headers=" + headers + + ", mappedHeaders=" + mappedHeaders + + ", fileData=" + Arrays.toString(fileData) + + ", mandatoryFields=" + mandatoryFields + + ", supportedFields=" + supportedFields + + ", processId='" + processId + '\'' + + ", values=" + values + + '}'; + } + + public String getProcessId() { + return processId; + } + + + public static class ShadowUserUploadBuilder { + + private String fileSize; + private List headers; + private byte[] fileData; + private ListmandatoryFields; + private ListsupportedFields; + private String processId; + private Listvalues; + private ListmappedHeaders; + + public ShadowUserUploadBuilder() { + } + + public ShadowUserUploadBuilder setFileSize(String fileSize) { + this.fileSize = fileSize; + return this; + } + + public ShadowUserUploadBuilder setHeaders(List headers) { + this.headers = headers; + return this; + } + + public ShadowUserUploadBuilder setFileData(byte[] fileData) { + this.fileData = fileData; + return this; + } + public ShadowUserUploadBuilder setMandatoryFields(List mandatoryFields) { + mandatoryFields.replaceAll(String::toLowerCase); + this.mandatoryFields = mandatoryFields; + return this; + } + + public ShadowUserUploadBuilder setSupportedFields(List supportedFields) { + supportedFields.replaceAll(String::toLowerCase); + this.supportedFields = supportedFields; + return this; + } + + public ShadowUserUploadBuilder setProcessId(String processId){ + this.processId=processId; + return this; + } + + public ShadowUserUploadBuilder setValues(Listvalues){ + this.values=values; + return this; + } + + public ShadowUserUploadBuilder setMappedHeaders(List mappedHeaders) { + this.mappedHeaders = mappedHeaders; + return this; + } + + public ShadowUserUpload validate(){ + ShadowUserUpload migration=new ShadowUserUpload(this); + validate(migration); + return migration; + } + private void validate(ShadowUserUpload migration){ + UserBulkMigrationRequestValidator.getInstance(migration).validate(); + } + } +} diff --git a/actors/common/src/main/java/org/sunbird/common/ShadowUserProcessor.java b/actors/common/src/main/java/org/sunbird/common/ShadowUserProcessor.java new file mode 100644 index 000000000..97b5fd68c --- /dev/null +++ b/actors/common/src/main/java/org/sunbird/common/ShadowUserProcessor.java @@ -0,0 +1,500 @@ +package org.sunbird.common; + +import com.datastax.driver.core.ResultSet; +import com.datastax.driver.core.Row; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.util.concurrent.FutureCallback; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.StringUtils; +import org.sunbird.bean.ClaimStatus; +import org.sunbird.bean.ShadowUser; +import org.sunbird.cassandra.CassandraOperation; +import org.sunbird.common.factory.EsClientFactory; +import org.sunbird.common.inf.ElasticSearchService; +import org.sunbird.common.models.response.Response; +import org.sunbird.common.models.util.JsonKey; +import org.sunbird.common.models.util.LoggerEnum; +import org.sunbird.common.models.util.ProjectLogger; +import org.sunbird.common.models.util.ProjectUtil; +import org.sunbird.common.request.ExecutionContext; +import org.sunbird.dto.SearchDTO; +import org.sunbird.helper.ServiceFactory; +import org.sunbird.learner.util.UserFlagEnum; +import org.sunbird.learner.util.UserFlagUtil; +import org.sunbird.learner.util.Util; +import org.sunbird.models.user.UserType; +import org.sunbird.telemetry.util.TelemetryUtil; +import scala.concurrent.Future; + +import java.sql.Timestamp; +import java.util.*; + +public class ShadowUserProcessor { + private Util.DbInfo usrDbInfo = Util.dbInfoMap.get(JsonKey.USER_DB); + private CassandraOperation cassandraOperation = ServiceFactory.getInstance(); + private ObjectMapper mapper = new ObjectMapper(); + private Map hashTagIdMap = new HashMap<>(); + private Util.DbInfo bulkUploadDbInfo = Util.dbInfoMap.get(JsonKey.BULK_OP_DB); + private Map extOrgIdMap = new HashMap<>(); + private Map channelOrgIdMap = new HashMap<>(); + private String custodianOrgId; + private Map>processIdtelemetryCtxMap=new HashMap<>(); + + private ElasticSearchService elasticSearchService = EsClientFactory.getInstance(JsonKey.REST); + + public void process() { + processAllUnclaimedUser(); + ProjectLogger.log("ShadowUserProcessor:process:successfully processed shadow user Stage3 ended", LoggerEnum.INFO.name()); + } + + private void processSingleShadowUser(ShadowUser shadowUser) { + ProjectLogger.log("ShadowUserProcessor:processSingleShadowUser:started claming shadow user with processId: " + shadowUser.getProcessId(), LoggerEnum.INFO.name()); + updateUser(shadowUser); + } + + + /** + * this method will be called when the user is already claimed need to update the user + * + * @param shadowUser + */ + public void processClaimedUser(ShadowUser shadowUser) { + ProjectLogger.log("ShadowUserProcessor:processClaimedUser:started claming shadow user with processId: " + shadowUser.getProcessId(), LoggerEnum.INFO.name()); + String orgId = getOrgId(shadowUser); + Map esUser = (Map) ElasticSearchHelper.getResponseFromFuture(elasticSearchService.getDataByIdentifier(ProjectUtil.EsType.user.getTypeName(), shadowUser.getUserId())); + String userId = (String) esUser.get(JsonKey.ID); + String rootOrgId = (String) esUser.get(JsonKey.ROOT_ORG_ID); + ProjectLogger.log("ShadowUserProcessor:processClaimedUser:started: flag value got from es " + esUser.get(JsonKey.FLAGS_VALUE), LoggerEnum.INFO.name()); + int flagsValue = null != esUser.get(JsonKey.FLAGS_VALUE) ? (int) esUser.get(JsonKey.FLAGS_VALUE) : 0; // since we are migrating the user from custodian org to non custodian org. + ProjectLogger.log("ShadowUserProcessor:processClaimedUser:Got Flag Value " + flagsValue, LoggerEnum.INFO.name()); + if (!((String) esUser.get(JsonKey.FIRST_NAME)).equalsIgnoreCase(shadowUser.getName()) || ((int) esUser.get(JsonKey.STATUS)) != shadowUser.getUserStatus()) { + updateUserInUserTable(flagsValue, shadowUser.getUserId(), rootOrgId, shadowUser); + } + deleteUserFromOrganisations(shadowUser, rootOrgId, (List>) esUser.get(JsonKey.ORGANISATIONS)); + if (StringUtils.isNotBlank(orgId) && !getOrganisationIds(esUser).contains(orgId)) { + registerUserToOrg(userId, orgId); + } + syncUserToES(userId); + updateUserInShadowDb(userId, shadowUser, ClaimStatus.CLAIMED.getValue(), null); + } + + private boolean isRootOrgMatchedWithOrgId(String rootOrgId, String orgId) { + if (StringUtils.equalsIgnoreCase(rootOrgId, orgId)) { + return true; + } + return false; + } + + private void deleteUserFromOrganisations(ShadowUser shadowUser, String rootOrgId, List> organisations) { + organisations.stream().forEach(organisation -> { + String orgId = (String) organisation.get(JsonKey.ORGANISATION_ID); + if (!isRootOrgMatchedWithOrgId(rootOrgId, orgId)) { + String id = (String) organisation.get(JsonKey.ID); + updateStatusInUserOrg(shadowUser, id); + } + }); + } + + private void updateStatusInUserOrg(ShadowUser shadowUser, String id) { + Map propertiesMap = new WeakHashMap<>(); + propertiesMap.put(JsonKey.ID, id); + propertiesMap.put(JsonKey.IS_DELETED, true); + propertiesMap.put(JsonKey.UPDATED_BY, shadowUser.getAddedBy()); + propertiesMap.put(JsonKey.UPDATED_DATE, ProjectUtil.getFormattedDate()); + Response response = cassandraOperation.updateRecord(JsonKey.SUNBIRD, JsonKey.USER_ORG, propertiesMap); + ProjectLogger.log("ShadowUserProcessor:updateStatusInUserOrg:response from cassandra in updating user org ".concat(response + ""), LoggerEnum.INFO.name()); + } + + private List> getUserMatchedIdentifierFromES(ShadowUser shadowUser) { + Map request = new WeakHashMap<>(); + Map filters = new WeakHashMap<>(); + Map or = new WeakHashMap<>(); + if (StringUtils.isNotBlank(shadowUser.getEmail())) { + or.put(JsonKey.EMAIL, shadowUser.getEmail()); + } + if (StringUtils.isNotBlank(shadowUser.getPhone())) { + or.put(JsonKey.PHONE, shadowUser.getPhone()); + } + filters.put(JsonKey.ES_OR_OPERATION, or); + filters.put(JsonKey.ROOT_ORG_ID, getCustodianOrgId()); + request.put(JsonKey.FILTERS, filters); + ProjectLogger.log("ShadowUserProcessor:getUserMatchedIdentifierFromES:the filter prepared for elastic search with processId: " + shadowUser.getProcessId() + " :filters are:" + filters, LoggerEnum.INFO.name()); + SearchDTO searchDTO = ElasticSearchHelper.createSearchDTO(request); + searchDTO.setFields(new ArrayList(Arrays.asList(JsonKey.ID, JsonKey.CHANNEL, JsonKey.EMAIL, JsonKey.PHONE, JsonKey.ROOT_ORG_ID, JsonKey.FLAGS_VALUE, JsonKey.ORGANISATIONS, JsonKey.IS_DELETED, JsonKey.STATUS))); + Map response = (Map) ElasticSearchHelper.getResponseFromFuture(elasticSearchService.search(searchDTO, JsonKey.USER)); + ProjectLogger.log("ShadowUserProcessor:getUserMatchedIdentifierFromES:response got from elasticSearch is with processId: " + shadowUser.getProcessId() + " :response is" + response, LoggerEnum.INFO.name()); + return (List>) response.get(JsonKey.CONTENT); + } + + private void updateUser(ShadowUser shadowUser) { + List> esUser = getUserMatchedIdentifierFromES(shadowUser); + ProjectLogger.log("ShadowUserProcessor:updateUser:GOT ES RESPONSE FOR USER WITH SIZE " + esUser.size(), LoggerEnum.INFO.name()); + if (CollectionUtils.isNotEmpty(esUser)) { + if (esUser.size() == 1) { + ProjectLogger.log("ShadowUserProcessor:updateUser:Got single user:" + esUser + " :with processId" + shadowUser.getProcessId(), LoggerEnum.INFO.name()); + Map userMap = esUser.get(0); + if (!isSame(shadowUser, userMap)) { + ProjectLogger.log("ShadowUserProcessor:updateUser: provided user details doesn't match with existing user details with processId" + shadowUser.getProcessId() + userMap, LoggerEnum.INFO.name()); + String rootOrgId = getRootOrgIdFromChannel(shadowUser.getChannel()); + int flagsValue = null != userMap.get(JsonKey.FLAGS_VALUE) ? (int) userMap.get(JsonKey.FLAGS_VALUE) : 0; // since we are migrating the user from custodian org to non custodian org. + ProjectLogger.log("ShadowUserProcessor:processClaimedUser:Got Flag Value " + flagsValue, LoggerEnum.INFO.name()); + updateUserInUserTable(flagsValue, (String) userMap.get(JsonKey.ID), rootOrgId, shadowUser); + String orgIdFromOrgExtId = getOrgId(shadowUser); + updateUserOrg(orgIdFromOrgExtId, rootOrgId, userMap); + createUserExternalId((String) userMap.get(JsonKey.ID), shadowUser); + updateUserInShadowDb((String) userMap.get(JsonKey.ID), shadowUser, ClaimStatus.CLAIMED.getValue(), null); + syncUserToES((String) userMap.get(JsonKey.ID)); + } + } else if (esUser.size() > 1) { + ProjectLogger.log("ShadowUserProcessor:updateUser:GOT response from ES :" + esUser, LoggerEnum.INFO.name()); + updateUserInShadowDb(null, shadowUser, ClaimStatus.MULTIMATCH.getValue(), getMatchingUserIds(esUser)); + } + } else { + ProjectLogger.log("ShadowUserProcessor:updateUser:SKIPPING SHADOW USER:" + shadowUser.toString(), LoggerEnum.INFO.name()); + } + esUser.clear(); + + } + + + private void generateTelemetry(String userId,String rootOrgId,ShadowUser shadowUser){ + ExecutionContext.getCurrent().setRequestContext(getTelemetryContextByProcessId((String) shadowUser.getProcessId())); + ProjectLogger.log("ShadowUserProcessor:generateTelemetry:generate telemetry:" + shadowUser.toString(), LoggerEnum.INFO.name()); + Map targetObject = new HashMap<>(); + Map rollUp = new HashMap<>(); + rollUp.put("l1", rootOrgId); + List> correlatedObject = new ArrayList<>(); + ExecutionContext.getCurrent().getRequestContext().put(JsonKey.ROLLUP, rollUp); + TelemetryUtil.generateCorrelatedObject(shadowUser.getProcessId(), JsonKey.PROCESS_ID, null, correlatedObject); + targetObject = + TelemetryUtil.generateTargetObject( + userId, StringUtils.capitalize(JsonKey.USER), JsonKey.MIGRATION_USER_OBJECT, null); + TelemetryUtil.telemetryProcessingCall(mapper.convertValue(shadowUser,Map.class), targetObject, correlatedObject); + } + + + private List getMatchingUserIds(List> esUser) { + ProjectLogger.log("ShadowUserProcessor:getMatchingUserIds:GOT response from counting matchingUserIds:" + esUser.size(), LoggerEnum.INFO.name()); + List matchingUserIds = new ArrayList<>(); + esUser.stream().forEach(singleEsUser -> { + matchingUserIds.add((String) singleEsUser.get(JsonKey.ID)); + }); + return matchingUserIds; + } + + + private void updateUserOrg(String orgIdFromOrgExtId, String rootOrgId, Map userMap) { + deleteUserOrganisations(userMap); + ProjectLogger.log("ShadowUserProcessor:updateUserOrg:deleting user organisation completed no started registering user to org", LoggerEnum.INFO.name()); + registerUserToOrg((String) userMap.get(JsonKey.ID), rootOrgId); + if (StringUtils.isNotBlank(orgIdFromOrgExtId) && !StringUtils.equalsIgnoreCase(rootOrgId, orgIdFromOrgExtId)) { + ProjectLogger.log("ShadowUserProcessor:updateUserOrg:user also needs to register with sub org", LoggerEnum.INFO.name()); + registerUserToOrg((String) userMap.get(JsonKey.ID), orgIdFromOrgExtId); + } + } + + private void updateUserInUserTable(int flagValue, String userId, String rootOrgId, ShadowUser shadowUser) { + Map propertiesMap = new WeakHashMap<>(); + propertiesMap.put(JsonKey.FIRST_NAME, shadowUser.getName()); + propertiesMap.put(JsonKey.ID, userId); + if (!(UserFlagUtil.assignUserFlagValues(flagValue).get(JsonKey.STATE_VALIDATED))) { + ProjectLogger.log("ShadowUserProcessor:updateUserInUserTable: updating Flag Value", LoggerEnum.INFO.name()); + propertiesMap.put(JsonKey.FLAGS_VALUE, flagValue + UserFlagEnum.STATE_VALIDATED.getUserFlagValue()); + } + propertiesMap.put(JsonKey.UPDATED_BY, shadowUser.getAddedBy()); + propertiesMap.put(JsonKey.UPDATED_DATE, ProjectUtil.getFormattedDate()); + if (shadowUser.getUserStatus() == ProjectUtil.Status.ACTIVE.getValue()) { + propertiesMap.put(JsonKey.IS_DELETED, false); + propertiesMap.put(JsonKey.STATUS, ProjectUtil.Status.ACTIVE.getValue()); + } else { + propertiesMap.put(JsonKey.IS_DELETED, true); + propertiesMap.put(JsonKey.STATUS, ProjectUtil.Status.INACTIVE.getValue()); + } + propertiesMap.put(JsonKey.USER_TYPE, UserType.TEACHER.getTypeName()); + propertiesMap.put(JsonKey.CHANNEL, shadowUser.getChannel()); + propertiesMap.put(JsonKey.ROOT_ORG_ID, rootOrgId); + ProjectLogger.log("ShadowUserProcessor:updateUserInUserTable: properties map formed for user update: " + propertiesMap, LoggerEnum.INFO.name()); + Response response = cassandraOperation.updateRecord(usrDbInfo.getKeySpace(), usrDbInfo.getTableName(), propertiesMap); + ProjectLogger.log("ShadowUserProcessor:updateUserInUserTable:user is updated with shadow user:RESPONSE FROM CASSANDRA IS:"+response.getResult(),LoggerEnum.INFO.name()); + generateTelemetry(userId,rootOrgId,shadowUser); + } + + + private String getRootOrgIdFromChannel(String channel) { + String rootOrgId = channelOrgIdMap.get(channel); + if (StringUtils.isNotBlank(rootOrgId)) { + ProjectLogger.log("ShadowUserProcessor:getRootOrgIdFromChannel: found rootorgid in cache " + rootOrgId, LoggerEnum.INFO.name()); + return rootOrgId; + } + Map request = new WeakHashMap<>(); + Map filters = new WeakHashMap<>(); + filters.put(JsonKey.CHANNEL, channel); + filters.put(JsonKey.IS_ROOT_ORG, true); + request.put(JsonKey.FILTERS, filters); + SearchDTO searchDTO = ElasticSearchHelper.createSearchDTO(request); + searchDTO.getAdditionalProperties().put(JsonKey.FILTERS, filters); + Future> esResultF = elasticSearchService.search(searchDTO, ProjectUtil.EsType.organisation.getTypeName()); + Map esResult = (Map) ElasticSearchHelper.getResponseFromFuture(esResultF); + if (MapUtils.isNotEmpty(esResult) && CollectionUtils.isNotEmpty((List) esResult.get(JsonKey.CONTENT))) { + Map esContent = ((List>) esResult.get(JsonKey.CONTENT)).get(0); + channelOrgIdMap.put(channel, (String) esContent.get(JsonKey.ID)); + return (String) esContent.get(JsonKey.ID); + } + return StringUtils.EMPTY; + } + + + /** + * this method + * + * @return + */ + private String getCustodianOrgId() { + if (StringUtils.isNotBlank(custodianOrgId)) { + ProjectLogger.log("ShadowUserProcessor:getCustodianOrgId:CUSTODIAN ORD ID FOUND in cache:" + custodianOrgId, LoggerEnum.INFO.name()); + return custodianOrgId; + } + Response response = cassandraOperation.getRecordById(JsonKey.SUNBIRD, JsonKey.SYSTEM_SETTINGS_DB, JsonKey.CUSTODIAN_ORG_ID); + List> result = new ArrayList<>(); + if (!((List) response.getResult().get(JsonKey.RESPONSE)).isEmpty()) { + result = ((List) response.getResult().get(JsonKey.RESPONSE)); + Map resultMap = result.get(0); + custodianOrgId = (String) resultMap.get(JsonKey.VALUE); + ProjectLogger.log("ShadowUserProcessor:getCustodianOrgId:CUSTODIAN ORD ID FOUND in DB:" + custodianOrgId, LoggerEnum.INFO.name()); + + } + + if (StringUtils.isBlank(custodianOrgId)) { + ProjectLogger.log("ShadowUserProcessor:getCustodianOrgId:No CUSTODIAN ORD ID FOUND PLEASE HAVE THAT IN YOUR ENVIRONMENT", LoggerEnum.ERROR.name()); + System.exit(-1); + } + return custodianOrgId; + } + + private FutureCallback getSyncCallback() { + return new FutureCallback() { + @Override + public void onSuccess(ResultSet result) { + Map columnMap = CassandraUtil.fetchColumnsMapping(result); + try { + Iterator resultIterator = result.iterator(); + while (resultIterator.hasNext()) { + Row row = resultIterator.next(); + Map doc = syncDataForEachRow(row, columnMap); + ShadowUser singleShadowUser = mapper.convertValue(doc, ShadowUser.class); + processSingleShadowUser(singleShadowUser); + ProjectLogger.log("ShadowUserProcessor:getSyncCallback:SUCCESS:SYNC CALLBACK SUCCESSFULLY PROCESSED for Shadow user: " + singleShadowUser.toString(), LoggerEnum.INFO.name()); + } + ProjectLogger.log("ShadowUserProcessor:getSyncCallback:SUCCESS:SYNC CALLBACK SUCCESSFULLY MIGRATED ALL Shadow user", LoggerEnum.INFO.name()); + + } catch (Exception e) { + ProjectLogger.log("ShadowUserProcessor:getSyncCallback:SUCCESS:ERROR OCCURRED WHILE GETTING SYNC CALLBACKS" + e, LoggerEnum.ERROR.name()); + } + } + + @Override + public void onFailure(Throwable t) { + ProjectLogger.log("ShadowUserProcessor:getSyncCallback:FAILURE:ERROR OCCURRED WHILE GETTING SYNC CALLBACKS" + t, LoggerEnum.ERROR.name()); + } + }; + } + + private Map syncDataForEachRow(Row row, Map columnMap) { + Map rowMap = new HashMap<>(); + columnMap + .entrySet() + .forEach(entry -> { + Object value = row.getObject(entry.getValue()); + rowMap.put(entry.getKey(), value); + }); + ProjectLogger.log("ShadowUserProcessor:syncDataForEachRow:row map returned " + rowMap, LoggerEnum.INFO.name()); + return rowMap; + } + + + + private void processAllUnclaimedUser(){ + + ProjectLogger.log("ShadowUserProcessor:processAllUnclaimedUser:started processing all unclaimed user", LoggerEnum.INFO.name()); + getUnclaimedRowsFromShadowUserDb(); + } + + private void getUnclaimedRowsFromShadowUserDb() { + Map propertiesMap = new WeakHashMap<>(); + propertiesMap.put(JsonKey.CLAIM_STATUS, ClaimStatus.UNCLAIMED.getValue()); + cassandraOperation.applyOperationOnRecordsAsync(JsonKey.SUNBIRD, JsonKey.SHADOW_USER, propertiesMap, null, getSyncCallback()); + propertiesMap.clear(); + } + + private boolean isSame(ShadowUser shadowUser, Map esUserMap) { + String orgId = getOrgId(shadowUser); + if (!shadowUser.getName().equalsIgnoreCase((String) esUserMap.get(JsonKey.FIRST_NAME))) { + return false; + } + if (StringUtils.isNotBlank(orgId) && !getOrganisationIds(esUserMap).contains(orgId)) { + return false; + } + if (shadowUser.getUserStatus() != (int) (esUserMap.get(JsonKey.STATUS))) { + return false; + } + if (StringUtils.isBlank(orgId)) { + return false; + } + return true; + } + + + private void updateUserInShadowDb(String userId, ShadowUser shadowUser, int claimStatus, List matchingUserIds) { + Map propertiesMap = new HashMap<>(); + propertiesMap.put(JsonKey.CLAIM_STATUS, claimStatus); + propertiesMap.put(JsonKey.PROCESS_ID, shadowUser.getProcessId()); + if (claimStatus == ClaimStatus.CLAIMED.getValue()) { + propertiesMap.put(JsonKey.CLAIMED_ON, new Timestamp(System.currentTimeMillis())); + propertiesMap.put(JsonKey.USER_ID, userId); + } + if (null != matchingUserIds && CollectionUtils.isNotEmpty(matchingUserIds)) { + propertiesMap.put(JsonKey.USER_IDs, matchingUserIds); + } + Map compositeKeysMap = new HashMap<>(); + compositeKeysMap.put(JsonKey.CHANNEL, shadowUser.getChannel()); + compositeKeysMap.put(JsonKey.USER_EXT_ID, shadowUser.getUserExtId()); + Response response = cassandraOperation.updateRecord(JsonKey.SUNBIRD, JsonKey.SHADOW_USER, propertiesMap, compositeKeysMap); + ProjectLogger.log("ShadowUserProcessor:updateUserInShadowDb:update:with processId: " + shadowUser.getProcessId() + " :and response is:" + response, LoggerEnum.INFO.name()); + } + + private String getOrgId(ShadowUser shadowUser) { + if (StringUtils.isNotBlank(shadowUser.getOrgExtId())) { + String orgId = extOrgIdMap.get(shadowUser.getChannel().concat(":").concat(shadowUser.getOrgExtId())); + if (StringUtils.isNotBlank(orgId)) { + return orgId; + } + Map request = new HashMap<>(); + Map filters = new HashMap<>(); + filters.put(JsonKey.EXTERNAL_ID, shadowUser.getOrgExtId().toLowerCase()); + filters.put(JsonKey.CHANNEL, shadowUser.getChannel()); + request.put(JsonKey.FILTERS, filters); + ProjectLogger.log("ShadowUserProcessor:getOrgId: request map prepared to query elasticsearch for org id :" + filters + "with processId" + shadowUser.getProcessId(), LoggerEnum.INFO.name()); + SearchDTO searchDTO = ElasticSearchHelper.createSearchDTO(request); + Map response = (Map) ElasticSearchHelper.getResponseFromFuture(elasticSearchService.search(searchDTO, ProjectUtil.EsType.organisation.getTypeName())); + List> orgData = ((List>) response.get(JsonKey.CONTENT)); + if (CollectionUtils.isNotEmpty(orgData)) { + Map orgMap = orgData.get(0); + extOrgIdMap.put(shadowUser.getChannel().concat(":").concat(shadowUser.getOrgExtId()), (String) orgMap.get(JsonKey.ID)); + return (String) orgMap.get(JsonKey.ID); + } + } + return StringUtils.EMPTY; + } + + + private List getOrganisationIds(Map dbUser) { + List organisationsIds = new ArrayList<>(); + ((List>) dbUser.get(JsonKey.ORGANISATIONS)).stream().forEach(organisation -> { + organisationsIds.add((String) organisation.get(JsonKey.ORGANISATION_ID)); + }); + return organisationsIds; + } + + + private void syncUserToES(String userId) { + Map fullUserDetails = Util.getUserDetails(userId, null); + try { + Future future = elasticSearchService.update(JsonKey.USER, userId, fullUserDetails); + if ((boolean) ElasticSearchHelper.getResponseFromFuture(future)) { + ProjectLogger.log("ShadowUserMigrationScheduler:updateUserStatus: data successfully updated to elastic search with userId:".concat(userId + ""), LoggerEnum.INFO.name()); + } + } catch (Exception e) { + e.printStackTrace(); + ProjectLogger.log("ShadowUserMigrationScheduler:syncUserToES: data failed to updates in elastic search with userId:".concat(userId + ""), LoggerEnum.ERROR.name()); + } + } + + + private void deleteUserOrganisations(Map esUserMap) { + ((List>) esUserMap.get(JsonKey.ORGANISATIONS)).stream().forEach(organisation -> { + String id = (String) organisation.get(JsonKey.ID); + deleteOrgFromUserOrg(id); + }); + } + + private void deleteOrgFromUserOrg(String id) { + Response response = cassandraOperation.deleteRecord(JsonKey.SUNBIRD, JsonKey.USER_ORG, id); + ProjectLogger.log("ShadowUserProcessor:deleteOrgFromUserOrg:user org is deleted ".concat(response.getResult() + ""), LoggerEnum.INFO.name()); + } + + + private void registerUserToOrg(String userId, String organisationId) { + Map reqMap = new WeakHashMap<>(); + List roles = new ArrayList<>(); + roles.add(ProjectUtil.UserRole.PUBLIC.getValue()); + reqMap.put(JsonKey.ROLES, roles); + String hashTagId = hashTagIdMap.get(organisationId); + if (StringUtils.isBlank(hashTagId)) { + hashTagId = Util.getHashTagIdFromOrgId(organisationId); + hashTagIdMap.put(organisationId, hashTagId); + } + reqMap.put(JsonKey.HASHTAGID, hashTagId); + reqMap.put(JsonKey.ID, ProjectUtil.getUniqueIdFromTimestamp(1)); + reqMap.put(JsonKey.USER_ID, userId); + reqMap.put(JsonKey.ORGANISATION_ID, organisationId); + reqMap.put(JsonKey.ORG_JOIN_DATE, ProjectUtil.getFormattedDate()); + reqMap.put(JsonKey.IS_DELETED, false); + Util.DbInfo usrOrgDb = Util.dbInfoMap.get(JsonKey.USR_ORG_DB); + try { + Response response = cassandraOperation.insertRecord(usrOrgDb.getKeySpace(), usrOrgDb.getTableName(), reqMap); + ProjectLogger.log("ShadowUserProcessor:registerUserToOrg:user status while registration with org is:" + response.getResult(), LoggerEnum.INFO.name()); + + } catch (Exception e) { + ProjectLogger.log("ShadowUserProcessor:registerUserToOrg:user is failed to register with org" + userId, LoggerEnum.ERROR.name()); + } + } + + private void createUserExternalId(String userId, ShadowUser shadowUser) { + Map externalId = new WeakHashMap<>(); + externalId.put(JsonKey.ID_TYPE, shadowUser.getChannel().toLowerCase()); + externalId.put(JsonKey.PROVIDER, shadowUser.getChannel().toLowerCase()); + externalId.put(JsonKey.EXTERNAL_ID, shadowUser.getUserExtId().toLowerCase()); + externalId.put(JsonKey.ORIGINAL_EXTERNAL_ID, externalId.get(JsonKey.EXTERNAL_ID)); + externalId.put(JsonKey.ORIGINAL_PROVIDER, externalId.get(JsonKey.PROVIDER)); + externalId.put(JsonKey.ORIGINAL_ID_TYPE, externalId.get(JsonKey.ID_TYPE)); + externalId.put(JsonKey.USER_ID, userId); + externalId.put(JsonKey.CREATED_BY, shadowUser.getAddedBy()); + externalId.put(JsonKey.CREATED_ON, new Timestamp(System.currentTimeMillis())); + ProjectLogger.log("ShadowUserProcessor:createUserExternalId:map prepared for user externalid is " + externalId + "with processId" + shadowUser.getProcessId(), LoggerEnum.INFO.name()); + saveUserExternalId(externalId); + } + + + /** + * this method will save user data in usr_external_identity table + * + * @param externalId + */ + private void saveUserExternalId(Map externalId) { + Response response = cassandraOperation.insertRecord(JsonKey.SUNBIRD, JsonKey.USR_EXT_IDNT_TABLE, externalId); + ProjectLogger.log("ShadowUserProcessor:createUserExternalId:response from cassandra ".concat(response.getResult() + ""), LoggerEnum.INFO.name()); + } + + + private Map getTelemetryContextByProcessId(String processId){ + + if(MapUtils.isNotEmpty(processIdtelemetryCtxMap.get(processId))){ + return processIdtelemetryCtxMap.get(processId); + } + MapcontextMap=new HashMap<>(); + MaptelemetryContext=new HashMap<>(); + Response response=cassandraOperation.getRecordById(bulkUploadDbInfo.getKeySpace(),bulkUploadDbInfo.getTableName(),processId); + List> result = new ArrayList<>(); + if (!((List) response.getResult().get(JsonKey.RESPONSE)).isEmpty()) { + result = ((List) response.getResult().get(JsonKey.RESPONSE)); + MapresponseMap=result.get(0); + contextMap=(Map)responseMap.get(JsonKey.CONTEXT_TELEMETRY); + telemetryContext.putAll(contextMap); + processIdtelemetryCtxMap.put(processId,telemetryContext); + } + ProjectLogger.log("ShadowUserMigrationScheduler:getFullRecordFromProcessId:got single row data from bulk_upload_process with processId:"+processId,LoggerEnum.INFO.name()); + return telemetryContext; + } +} + diff --git a/actors/common/src/main/java/org/sunbird/common/quartz/scheduler/SchedulerManager.java b/actors/common/src/main/java/org/sunbird/common/quartz/scheduler/SchedulerManager.java index c5fdf63bc..e843ce684 100644 --- a/actors/common/src/main/java/org/sunbird/common/quartz/scheduler/SchedulerManager.java +++ b/actors/common/src/main/java/org/sunbird/common/quartz/scheduler/SchedulerManager.java @@ -52,16 +52,17 @@ private void schedule() { configProp = setUpClusterMode(); } if (!isEmbedded && configProp != null) { - ProjectLogger.log("Quartz scheduler is running in cluster mode."); + ProjectLogger.log("Quartz scheduler is running in cluster mode.", LoggerEnum.INFO.name()); scheduler = new StdSchedulerFactory(configProp).getScheduler(); } else { - ProjectLogger.log("Quartz scheduler is running in embedded mode."); + ProjectLogger.log("Quartz scheduler is running in embedded mode.", LoggerEnum.INFO.name()); scheduler = new StdSchedulerFactory().getScheduler(); } String identifier = "NetOps-PC1502295457753"; scheduleBulkUploadJob(identifier); scheduleUpdateUserCountJob(identifier); scheduleChannelReg(identifier); + scheduleShadowUser(identifier); } catch (Exception e) { ProjectLogger.log( "SchedulerManager:schedule: Error in starting scheduler jobs - org.sunbird.common.quartz.scheduler.SchedulerManager ", @@ -252,4 +253,39 @@ public static void registerShutDownHook() { "SchedulerManager:registerShutDownHook: ShutDownHook registered for Quartz scheduler.", LoggerEnum.INFO); } + + private void scheduleShadowUser(String identifier) { + ProjectLogger.log( + "SchedulerManager:scheduleShadowUser:scheduleShadowUser scheduler started", + LoggerEnum.INFO.name()); + ProjectLogger.log( + "SchedulerManager:scheduleShadowUser:scheduleShadowUser scheduler started seconde log", + LoggerEnum.INFO.name()); + JobDetail migrateShadowUserJob = + JobBuilder.newJob(ShadowUserMigrationScheduler.class) + .requestRecovery(true) + .withDescription("Scheduler for migrating shadow user ") + .withIdentity("migrateShadowUserScheduler", identifier) + .build(); + Trigger migrateShadowUserTrigger = + TriggerBuilder.newTrigger() + .withIdentity("migrateShadowUserTrigger", identifier) + .withSchedule( + CronScheduleBuilder.cronSchedule( + PropertiesCache.getInstance() + .getProperty("quartz_shadow_user_migration_timer"))) + .build(); + try { + if (scheduler.checkExists(migrateShadowUserJob.getKey())) { + scheduler.deleteJob(migrateShadowUserJob.getKey()); + } + scheduler.scheduleJob(migrateShadowUserJob, migrateShadowUserTrigger); + scheduler.start(); + ProjectLogger.log( + "SchedulerManager:scheduleShadowUser:scheduleShadowUser scheduler ended", + LoggerEnum.INFO.name()); + } catch (Exception e) { + ProjectLogger.log(e.getMessage(), e); + } + } } diff --git a/actors/common/src/main/java/org/sunbird/common/quartz/scheduler/ShadowUserMigrationScheduler.java b/actors/common/src/main/java/org/sunbird/common/quartz/scheduler/ShadowUserMigrationScheduler.java new file mode 100644 index 000000000..8e4e23601 --- /dev/null +++ b/actors/common/src/main/java/org/sunbird/common/quartz/scheduler/ShadowUserMigrationScheduler.java @@ -0,0 +1,378 @@ +package org.sunbird.common.quartz.scheduler; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.StringUtils; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.sunbird.bean.ClaimStatus; +import org.sunbird.bean.MigrationUser; +import org.sunbird.bean.ShadowUser; +import org.sunbird.cassandra.CassandraOperation; +import org.sunbird.common.ElasticSearchHelper; +import org.sunbird.common.ShadowUserProcessor; +import org.sunbird.common.factory.EsClientFactory; +import org.sunbird.common.inf.ElasticSearchService; +import org.sunbird.common.models.response.Response; +import org.sunbird.common.models.util.JsonKey; +import org.sunbird.common.models.util.LoggerEnum; +import org.sunbird.common.models.util.ProjectLogger; +import org.sunbird.common.models.util.ProjectUtil; +import org.sunbird.common.models.util.datasecurity.DecryptionService; +import org.sunbird.common.models.util.datasecurity.EncryptionService; +import org.sunbird.dto.SearchDTO; +import org.sunbird.helper.ServiceFactory; +import org.sunbird.learner.actors.bulkupload.model.BulkMigrationUser; +import org.sunbird.learner.util.Util; + +import java.sql.Timestamp; +import java.util.*; + +public class ShadowUserMigrationScheduler extends BaseJob{ + + private Util.DbInfo bulkUploadDbInfo = Util.dbInfoMap.get(JsonKey.BULK_OP_DB); + private CassandraOperation cassandraOperation = ServiceFactory.getInstance(); + private ObjectMapper mapper = new ObjectMapper(); + private HashSetverifiedChannelOrgExternalIdSet=new HashSet<>(); + private ElasticSearchService elasticSearchService = EsClientFactory.getInstance(JsonKey.REST); + private DecryptionService decryptionService = org.sunbird.common.models.util.datasecurity.impl.ServiceFactory.getDecryptionServiceInstance(null); + private EncryptionService encryptionService = org.sunbird.common.models.util.datasecurity.impl.ServiceFactory.getEncryptionServiceInstance(null); + + + + @Override + public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { + ProjectLogger.log( + "ShadowUserMigrationScheduler:execute:Running Shadow User Upload Scheduler Job at: " + + Calendar.getInstance().getTime() + + " triggered by: " + + jobExecutionContext.getJobDetail().toString(), + LoggerEnum.INFO.name()); + Util.initializeContextForSchedulerJob( + JsonKey.SYSTEM, jobExecutionContext.getFireInstanceId(), JsonKey.SCHEDULER_JOB); + startMigration(); + } + + + public void startMigration(){ + List unprocessedRecordIds = getUnprocessedRecordIds(); + ProjectLogger.log("ShadowUserMigrationScheduler:startMigration:Got Bulk Upload Db migrations started",LoggerEnum.INFO.name()); + ProjectLogger.log("ShadowUserMigrationScheduler:startMigration:Got Bulk Upload Db unprocessed and failed records size is:"+unprocessedRecordIds.size(),LoggerEnum.INFO.name()); + processRecords(unprocessedRecordIds); + ShadowUserProcessor processorObject=new ShadowUserProcessor(); + processorObject.process(); + unprocessedRecordIds.clear(); + ProjectLogger.log("ShadowUserMigrationScheduler:processRecords:Scheduler Job Ended for ShadowUser Migration",LoggerEnum.INFO.name()); + ProjectLogger.log("ShadowUserMigrationScheduler:execute:Scheduler Job ended for shadow user migration",LoggerEnum.INFO.name()); + } + + /** + * - fetch rows from bulk upload table whose status is less than 2 + * - update the bulk upload table row status to processing + * - start processing single row + * - fetch the single row data + * - Get List of Migration(csv) user from the row + * - process each Migration(csv) user + * - update the bulk upload table row status to completed and add message to success + * - if fails update the bulk upload row status to failed and add failureResult + */ + private void processRecords(List unprocessedRecordIds) { + ProjectLogger.log("ShadowUserMigrationScheduler:processRecords:Scheduler Job Started for ShadowUser Migration",LoggerEnum.INFO.name()); + unprocessedRecordIds.stream().forEach(id -> { + Maprow=getFullRecordFromProcessId(id); + BulkMigrationUser bulkMigrationUser = convertRowToObject(row); + row.clear(); + try { + updateStatusInUserBulkTable(bulkMigrationUser.getId(), ProjectUtil.BulkProcessStatus.IN_PROGRESS.getValue()); + List migrationUserList = getMigrationUserAsList(bulkMigrationUser); + migrationUserList.parallelStream().forEach(singleMigrationUser -> { + encryptEmailAndPhone(singleMigrationUser); + processSingleMigUser(bulkMigrationUser.getCreatedBy(),bulkMigrationUser.getId(), singleMigrationUser); + }); + updateMessageInBulkUserTable(bulkMigrationUser.getId(), JsonKey.SUCCESS_RESULT, JsonKey.SUCCESS); + } catch (Exception e) { + ProjectLogger.log("ShadowUserMigrationScheduler:processRecords:error occurred ".concat(e+""),LoggerEnum.ERROR.name()); + updateMessageInBulkUserTable(bulkMigrationUser.getId(), JsonKey.FAILURE_RESULT, e.getMessage()); + } + }); + ProjectLogger.log("ShadowUserMigrationScheduler:processRecords:started stage3__________________________-",LoggerEnum.INFO.name()); + } + + private void processSingleMigUser(String createdBy,String processId, MigrationUser singleMigrationUser) { + ProjectLogger.log("ShadowUserMigrationScheduler:processSingleMigUser:Single migration User Started processing with processId:"+processId,LoggerEnum.INFO.name()); + Map existingUserDetails = getShadowExistingUserDetails(singleMigrationUser.getChannel(), singleMigrationUser.getUserExternalId()); + if (MapUtils.isEmpty(existingUserDetails)) { + ProjectLogger.log("ShadowUserMigrationScheduler:processSingleMigUser:existing user not found with processId:"+processId,LoggerEnum.INFO.name()); + insertShadowUserToDb(createdBy,processId, singleMigrationUser); + } else { + ProjectLogger.log("ShadowUserMigrationScheduler:processSingleMigUser:existing user found with processId:"+processId,LoggerEnum.INFO.name()); + ShadowUser shadowUser = mapper.convertValue(existingUserDetails, ShadowUser.class); + updateUser(processId,singleMigrationUser, shadowUser); + } + } + + + /** + * this method will read rows from the bulk_upload_process table who has status less than 2 + * @return list + */ + private List getUnprocessedRecordIds() { + Response response = cassandraOperation.getRecordByObjectType(bulkUploadDbInfo.getKeySpace(), bulkUploadDbInfo.getTableName(),JsonKey.ID, JsonKey.STATUS, ProjectUtil.BulkProcessStatus.INTERRUPT.getValue(),JsonKey.MIGRATION_USER_OBJECT); + List> result = new ArrayList<>(); + if (!((List) response.getResult().get(JsonKey.RESPONSE)).isEmpty()) { + result = ((List) response.getResult().get(JsonKey.RESPONSE)); + } + ListprocessIds=new ArrayList<>(); + result.stream().forEach(rowMap->{ + processIds.add((String) rowMap.get(JsonKey.ID)); + }); + ProjectLogger.log("ShadowUserMigrationScheduler:getUnprocessedRecordIds:got rows from Bulk user table is:".concat(result.size()+""),LoggerEnum.INFO.name()); + return processIds; + } + + + private Map getFullRecordFromProcessId(String processId){ + + int FIRST_RECORD=0; + Response response = cassandraOperation.getRecordById(bulkUploadDbInfo.getKeySpace(), bulkUploadDbInfo.getTableName(),processId); + List> result = new ArrayList<>(); + if (!((List) response.getResult().get(JsonKey.RESPONSE)).isEmpty()) { + result = ((List) response.getResult().get(JsonKey.RESPONSE)); + } + ProjectLogger.log("ShadowUserMigrationScheduler:getFullRecordFromProcessId:got single row data from bulk_upload_process with processId:"+processId,LoggerEnum.INFO.name()); + return result.get(FIRST_RECORD); + } + + + private BulkMigrationUser convertRowToObject(Map row) { + BulkMigrationUser bulkMigrationUser = null; + try { + bulkMigrationUser = mapper.convertValue(row, BulkMigrationUser.class); + } catch (Exception e) { + e.printStackTrace(); + ProjectLogger.log("ShadowUserMigrationScheduler:convertMapToMigrationObject:error occurred while converting map to pojo".concat(e.getMessage() + ""), LoggerEnum.ERROR.name()); + } + return bulkMigrationUser; + } + + private List getMigrationUserAsList(BulkMigrationUser bulkMigrationUser) { + List migrationUserList = new ArrayList<>(); + try { + String decryptedData = decryptionService.decryptData(bulkMigrationUser.getData()); + migrationUserList = mapper.readValue(decryptedData, new TypeReference>() { + }); + } catch (Exception e) { + e.printStackTrace(); + ProjectLogger.log("ShadowUserMigrationScheduler:getMigrationUserAsList:error occurred while converting map to POJO: "+e, LoggerEnum.ERROR.name()); + } + return migrationUserList; + } + + private Map getShadowExistingUserDetails(String channel, String userExtId) { + Map propertiesMap = new WeakHashMap<>(); + propertiesMap.put(JsonKey.CHANNEL, channel); + propertiesMap.put("userExtId", userExtId); + Map result = new HashMap<>(); + Response response = cassandraOperation.getRecordsByProperties(JsonKey.SUNBIRD, JsonKey.SHADOW_USER, propertiesMap); + if (!((List) response.getResult().get(JsonKey.RESPONSE)).isEmpty()) { + result = ((Map) ((List) response.getResult().get(JsonKey.RESPONSE)).get(0)); + } + return result; + } + + /** + * this method will prepare the shawdow user object and insert the record into shawdow_user table + * @param createdBy + * @param processId + * @param migrationUser + */ + private void insertShadowUserToDb(String createdBy,String processId, MigrationUser migrationUser) { + Map dbMap = new WeakHashMap<>(); + dbMap.put(JsonKey.USER_EXT_ID,migrationUser.getUserExternalId()); + dbMap.put(JsonKey.ORG_EXT_ID,migrationUser.getOrgExternalId()); + ProjectLogger.log("ShadowUserMigrationScheduler:insertShadowUser: email got "+migrationUser.getEmail()+" "+migrationUser.getPhone(), LoggerEnum.INFO.name()); + dbMap.put(JsonKey.EMAIL,migrationUser.getEmail()); + dbMap.put(JsonKey.PHONE,migrationUser.getPhone()); + dbMap.put(JsonKey.ADDED_BY,createdBy); + dbMap.put(JsonKey.CHANNEL,migrationUser.getChannel()); + dbMap.put(JsonKey.NAME,migrationUser.getName()); + dbMap.put(JsonKey.PROCESS_ID,processId); + dbMap.put(JsonKey.CLAIM_STATUS,ClaimStatus.UNCLAIMED.getValue()); + dbMap.put(JsonKey.USER_STATUS,getInputStatus(migrationUser.getInputStatus())); + dbMap.put(JsonKey.CREATED_ON, new Timestamp(System.currentTimeMillis())); + if(!isOrgExternalIdValid(migrationUser)) { + dbMap.put(JsonKey.CLAIM_STATUS,ClaimStatus.ORGEXTERNALIDMISMATCH.getValue()); + } + Response response = cassandraOperation.insertRecord(JsonKey.SUNBIRD, JsonKey.SHADOW_USER, dbMap); + dbMap.clear(); + ProjectLogger.log("ShadowUserMigrationScheduler:insertShadowUser: record status in cassandra ".concat(response+ ""), LoggerEnum.INFO.name()); + } + + private int getInputStatus(String inputStatus) { + if (inputStatus.equalsIgnoreCase(JsonKey.ACTIVE)) { + return ProjectUtil.Status.ACTIVE.getValue(); + } + return ProjectUtil.Status.INACTIVE.getValue(); + } + + private void updateUser(String processId,MigrationUser migrationUser, ShadowUser shadowUser) { + updateUserInShadowDb(processId,migrationUser,shadowUser); + } + + private void updateUserInShadowDb(String processId,MigrationUser migrationUser, ShadowUser shadowUser) { + if(!isSame(shadowUser,migrationUser)){ + Map propertiesMap = new WeakHashMap<>(); + propertiesMap.put(JsonKey.EMAIL,migrationUser.getEmail()); + propertiesMap.put(JsonKey.PHONE,migrationUser.getPhone()); + propertiesMap.put(JsonKey.PROCESS_ID,processId); + propertiesMap.put(JsonKey.NAME, migrationUser.getName()); + propertiesMap.put(JsonKey.ORG_EXT_ID, migrationUser.getOrgExternalId()); + propertiesMap.put(JsonKey.UPDATED_ON, new Timestamp(System.currentTimeMillis())); + propertiesMap.put(JsonKey.USER_STATUS,getInputStatus(migrationUser.getInputStatus())); + if(!isOrgExternalIdValid(migrationUser)) { + propertiesMap.put(JsonKey.CLAIM_STATUS, ClaimStatus.ORGEXTERNALIDMISMATCH.getValue()); + } + MapcompositeKeysMap=new HashMap<>(); + compositeKeysMap.put(JsonKey.CHANNEL, migrationUser.getChannel()); + compositeKeysMap.put(JsonKey.USER_EXT_ID,migrationUser.getUserExternalId()); + Response response = cassandraOperation.updateRecord(JsonKey.SUNBIRD, JsonKey.SHADOW_USER, propertiesMap,compositeKeysMap); + ProjectLogger.log("ShadowUserMigrationScheduler:updateUserInShadowDb: record status in cassandra ".concat(response+ ""), LoggerEnum.INFO.name()); + propertiesMap.clear(); + new ShadowUserProcessor().processClaimedUser(getUpdatedShadowUser(compositeKeysMap)); + } + } + + /** + * this method will return the updated shadow user object when new orgExtId , name is been passed + * @param compositeKeysMap + * @return shawdow user + */ + private ShadowUser getUpdatedShadowUser( MapcompositeKeysMap){ + Response response=cassandraOperation.getRecordsByCompositeKey(JsonKey.SUNBIRD, JsonKey.SHADOW_USER,compositeKeysMap); + ProjectLogger.log("ShadowUserMigrationScheduler:getUpdatedShadowUser: record status in cassandra for getting the updated shawdow user object ".concat(response.getResult() + ""), LoggerEnum.INFO.name()); + Mapresultmap=((List>)response.getResult().get(JsonKey.RESPONSE)).get(0); + ShadowUser shadowUser=mapper.convertValue(resultmap,ShadowUser.class); + return shadowUser; + } + + + private void updateStatusInUserBulkTable(String processId, int statusVal) { + try { + ProjectLogger.log("ShadowUserMigrationScheduler:updateStatusInUserBulkTable: got status to change in bulk upload table".concat(statusVal + "")+"with processId"+processId, LoggerEnum.INFO.name()); + Map propertiesMap = new WeakHashMap<>(); + propertiesMap.put(JsonKey.ID, processId); + propertiesMap.put(JsonKey.STATUS, statusVal); + updateBulkUserTable(propertiesMap); + } catch (Exception e) { + ProjectLogger.log("ShadowUserMigrationScheduler:updateStatusInUserBulkTable: status update failed".concat(e + "")+"with processId"+processId, LoggerEnum.ERROR.name()); + } + } + + private void updateBulkUserTable(Map propertiesMap) { + Response response = cassandraOperation.updateRecord(bulkUploadDbInfo.getKeySpace(), bulkUploadDbInfo.getTableName(), propertiesMap); + ProjectLogger.log("ShadowUserMigrationScheduler:updateBulkUserTable: status update result".concat(response+ ""), LoggerEnum.INFO.name()); + } + + private void updateMessageInBulkUserTable(String processId, String key, String value) { + Map propertiesMap = new WeakHashMap<>(); + propertiesMap.put(JsonKey.ID, processId); + propertiesMap.put(key, value); + if (StringUtils.equalsIgnoreCase(value,JsonKey.SUCCESS)) { + propertiesMap.put(JsonKey.STATUS, ProjectUtil.BulkProcessStatus.COMPLETED.getValue()); + propertiesMap.put(JsonKey.FAILURE_RESULT, ""); + } else { + propertiesMap.put(JsonKey.SUCCESS_RESULT, ""); + propertiesMap.put(JsonKey.STATUS, ProjectUtil.BulkProcessStatus.FAILED.getValue()); + } + updateBulkUserTable(propertiesMap); + propertiesMap.clear(); + } + + + /** + * this method will take descision wheather to update the record or not. + * @param shadowUser + * @param migrationUser + * @return boolean + */ + private boolean isSame(ShadowUser shadowUser,MigrationUser migrationUser){ + + if(StringUtils.isBlank(migrationUser.getOrgExternalId())){ + return false; + } + if(!shadowUser.getName().equalsIgnoreCase(migrationUser.getName())){ + return false; + } + if(StringUtils.isNotBlank(migrationUser.getEmail()) && !shadowUser.getEmail().equalsIgnoreCase(migrationUser.getEmail())){ + return false; + } + if(! StringUtils.equalsIgnoreCase(shadowUser.getOrgExtId(),migrationUser.getOrgExternalId())){ + return false; + } + if(StringUtils.isNotBlank(migrationUser.getPhone()) && !shadowUser.getPhone().equalsIgnoreCase(migrationUser.getPhone())) + { + return false; + } + if((getInputStatus(migrationUser.getInputStatus())!=shadowUser.getUserStatus())){ + return false; + } + + if(!migrationUser.getName().equalsIgnoreCase(shadowUser.getName())){ + return false; + } + return true; + } + + + /** + * this method will check weather the provided orgExternalId is correct or not + * @param migrationUser + * @return true if correct else false + */ + private boolean isOrgExternalIdValid(MigrationUser migrationUser) { + if (StringUtils.isBlank(migrationUser.getOrgExternalId())) { + return true; + } + else if(verifiedChannelOrgExternalIdSet.contains(migrationUser.getChannel()+":"+migrationUser.getOrgExternalId())){ + ProjectLogger.log("ShadowUserMigrationScheduler:isOrgExternalIdValid: found orgexternalid in cache:"+migrationUser.getOrgExternalId(), LoggerEnum.INFO.name()); + return true; + } + Map request = new WeakHashMap<>(); + Map filters = new WeakHashMap<>(); + filters.put(JsonKey.EXTERNAL_ID, migrationUser.getOrgExternalId()); + filters.put(JsonKey.CHANNEL, migrationUser.getChannel()); + request.put(JsonKey.FILTERS, filters); + SearchDTO searchDTO = ElasticSearchHelper.createSearchDTO(request); + searchDTO.setFields(new ArrayList<>(Arrays.asList(JsonKey.ID))); + Map response = (Map) ElasticSearchHelper.getResponseFromFuture(elasticSearchService.search(searchDTO, ProjectUtil.EsType.organisation.getTypeName())); + if(CollectionUtils.isNotEmpty((List>) response.get(JsonKey.CONTENT))){ + verifiedChannelOrgExternalIdSet.add(migrationUser.getChannel()+":"+migrationUser.getOrgExternalId()); + response.clear(); + return true; + } + response.clear(); + return false; + } + + private String encryptValue(String key) { + try { + return encryptionService.encryptData(key); + } catch (Exception e) { + ProjectLogger.log("ShadowUserMigrationScheduler:getEncryptedValue: error occurred in encrypting value "+key,LoggerEnum.ERROR.name()); + return key; + } + } + + private void encryptEmailAndPhone(MigrationUser migrationUser){ + + + if(StringUtils.isNotBlank(migrationUser.getEmail())){ + migrationUser.setEmail(encryptValue(migrationUser.getEmail().toLowerCase())); + } + if(StringUtils.isNotBlank(migrationUser.getPhone())){ + migrationUser.setPhone(encryptValue(migrationUser.getPhone())); + } + } +} \ No newline at end of file diff --git a/actors/common/src/main/java/org/sunbird/error/CsvError.java b/actors/common/src/main/java/org/sunbird/error/CsvError.java new file mode 100644 index 000000000..73de32380 --- /dev/null +++ b/actors/common/src/main/java/org/sunbird/error/CsvError.java @@ -0,0 +1,24 @@ +package org.sunbird.error; + +import java.util.ArrayList; +import java.util.List; + +public class CsvError { + + private List errorsList=new ArrayList<>(); + + public CsvError() { + } + + public List getErrorsList() { + return errorsList; + } + + public void setErrorsList(List errorsList) { + this.errorsList = errorsList; + } + + public void setError(CsvRowErrorDetails errorDetails){ + errorsList.add(errorDetails); + } +} diff --git a/actors/common/src/main/java/org/sunbird/error/CsvErrorDispatcher.java b/actors/common/src/main/java/org/sunbird/error/CsvErrorDispatcher.java new file mode 100644 index 000000000..524d62dfc --- /dev/null +++ b/actors/common/src/main/java/org/sunbird/error/CsvErrorDispatcher.java @@ -0,0 +1,38 @@ +package org.sunbird.error; + +import org.sunbird.common.exception.ProjectCommonException; +import org.sunbird.common.responsecode.ResponseCode; + +import java.io.FileWriter; + + +/** + * this class will dispatch the errors in the csv format + * + * @author anmolgupta + */ +public class CsvErrorDispatcher implements IErrorDispatcher { + + + private CsvError error; + + private CsvErrorDispatcher(CsvError error) { + this.error = error; + } + + public static CsvErrorDispatcher getInstance(CsvError error) { + return new CsvErrorDispatcher(error); + } + + @Override + public void dispatchError() { + throw new ProjectCommonException( + ResponseCode.invalidRequestData.getErrorCode(), + error.getErrorsList().toString(), + ResponseCode.CLIENT_ERROR.getResponseCode()); + } + + + + +} diff --git a/actors/common/src/main/java/org/sunbird/error/CsvRowErrorDetails.java b/actors/common/src/main/java/org/sunbird/error/CsvRowErrorDetails.java new file mode 100644 index 000000000..89d8d295f --- /dev/null +++ b/actors/common/src/main/java/org/sunbird/error/CsvRowErrorDetails.java @@ -0,0 +1,50 @@ +package org.sunbird.error; + +public class CsvRowErrorDetails { + + int rowId; + private String header; + private ErrorEnum errorEnum; + + public CsvRowErrorDetails(int rowId, String header, ErrorEnum errorEnum) { + this.rowId = rowId; + this.header = header; + this.errorEnum = errorEnum; + } + + public CsvRowErrorDetails() { + } + + public int getRowId() { + return rowId; + } + + public void setRowId(int rowId) { + this.rowId = rowId; + } + + public String getHeader() { + return header; + } + + public void setHeader(String header) { + this.header = header; + } + + public ErrorEnum getErrorEnum() { + return errorEnum; + } + + public void setErrorEnum(ErrorEnum errorEnum) { + this.errorEnum = errorEnum; + } + + @Override + public String toString() { + return "ErrorDetails{" + + "rowId=" + rowId + + ", header='" + header + '\'' + + ", errorEnum=" + errorEnum + + '}'; + } +} diff --git a/actors/common/src/main/java/org/sunbird/error/ErrorEnum.java b/actors/common/src/main/java/org/sunbird/error/ErrorEnum.java new file mode 100644 index 000000000..ec32c9528 --- /dev/null +++ b/actors/common/src/main/java/org/sunbird/error/ErrorEnum.java @@ -0,0 +1,15 @@ +package org.sunbird.error; + +public enum ErrorEnum { + invalid("invalid"), + duplicate("duplicate"), + missing("missing"); + private String value; + ErrorEnum(String value) { + this.value=value; + } + + public String getValue() { + return value; + } +} diff --git a/actors/common/src/main/java/org/sunbird/error/IErrorDispatcher.java b/actors/common/src/main/java/org/sunbird/error/IErrorDispatcher.java new file mode 100644 index 000000000..56c92e203 --- /dev/null +++ b/actors/common/src/main/java/org/sunbird/error/IErrorDispatcher.java @@ -0,0 +1,16 @@ +package org.sunbird.error; + + +/** + * this is an interface class for the error dispatcher + * + * @author anmolgupta + */ +public interface IErrorDispatcher { + + + /** + * this method will prepare the error and will throw ProjectCommonException. + */ + void dispatchError(); +} diff --git a/actors/common/src/main/java/org/sunbird/error/ListErrorDispatcher.java b/actors/common/src/main/java/org/sunbird/error/ListErrorDispatcher.java new file mode 100644 index 000000000..65a0fb592 --- /dev/null +++ b/actors/common/src/main/java/org/sunbird/error/ListErrorDispatcher.java @@ -0,0 +1,39 @@ +package org.sunbird.error; + +import com.mchange.v1.util.ArrayUtils; +import org.sunbird.common.exception.ProjectCommonException; +import org.sunbird.common.responsecode.ResponseCode; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * this class will dispatch error in list format + * @author anmolgupta + */ +public class ListErrorDispatcher implements IErrorDispatcher { + + private CsvError error; + + private ListErrorDispatcher(CsvError error) { + this.error = error; + } + + public static ListErrorDispatcher getInstance(CsvError error){ + return new ListErrorDispatcher(error); + } + + @Override + public void dispatchError() { + Collections.sort(error.getErrorsList(), new RowComparator()); + Listerrors=new ArrayList<>(); + error.getErrorsList().stream().forEach(errorDetails -> { + errors.add(String.format("In Row %s:the Column %s:is %s",errorDetails.getRowId()+1,errorDetails.getHeader(),errorDetails.getErrorEnum().getValue())); + }); + throw new ProjectCommonException( + ResponseCode.invalidRequestData.getErrorCode(), + ArrayUtils.stringifyContents(errors.toArray()), + ResponseCode.CLIENT_ERROR.getResponseCode()); + } +} diff --git a/actors/common/src/main/java/org/sunbird/error/RowComparator.java b/actors/common/src/main/java/org/sunbird/error/RowComparator.java new file mode 100644 index 000000000..55d078046 --- /dev/null +++ b/actors/common/src/main/java/org/sunbird/error/RowComparator.java @@ -0,0 +1,10 @@ +package org.sunbird.error; + +import java.util.Comparator; + +public class RowComparator implements Comparator { + @Override + public int compare(CsvRowErrorDetails o1, CsvRowErrorDetails o2) { + return o1.rowId-o2.rowId; + } +} diff --git a/actors/common/src/main/java/org/sunbird/error/factory/ErrorDispatcherFactory.java b/actors/common/src/main/java/org/sunbird/error/factory/ErrorDispatcherFactory.java new file mode 100644 index 000000000..cd0baadbe --- /dev/null +++ b/actors/common/src/main/java/org/sunbird/error/factory/ErrorDispatcherFactory.java @@ -0,0 +1,45 @@ +package org.sunbird.error.factory; + +import org.sunbird.common.models.util.JsonKey; +import org.sunbird.common.models.util.LoggerEnum; +import org.sunbird.common.models.util.ProjectLogger; +import org.sunbird.common.models.util.PropertiesCache; +import org.sunbird.error.CsvError; +import org.sunbird.error.CsvErrorDispatcher; +import org.sunbird.error.IErrorDispatcher; +import org.sunbird.error.ListErrorDispatcher; + + +/** + * this is error dispatcher factory class which will judge type of error need to show on the basis of error count. + * @author anmolgupta + */ +public class ErrorDispatcherFactory { + + /** + * this ERROR_VISUALIZATION_THRESHOLD will decide in which need to show errors + */ + public static final int ERROR_VISUALIZATION_THRESHOLD = getErrorVisualizationThreshold(); + + /** + * this method will return the required error dispatcher class object + * @param error + * @return IErrorDispatcher + */ + public static IErrorDispatcher getErrorDispatcher(CsvError error) { + if (error.getErrorsList().size() > ERROR_VISUALIZATION_THRESHOLD) { + return CsvErrorDispatcher.getInstance(error); + } + return ListErrorDispatcher.getInstance(error); + } + + /** + * this method will return the ERROR_VISUALIZATION_THRESHOLD value + * @return int + */ + private static int getErrorVisualizationThreshold() { + String value = PropertiesCache.getInstance().readProperty(JsonKey.ERROR_VISUALIZATION_THRESHOLD); + ProjectLogger.log("ErrorDispatcherFactory:getErrorVisualizationThreshold:threshold got ".concat(value + ""), LoggerEnum.INFO.name()); + return Integer.parseInt(value); + } +} diff --git a/actors/common/src/main/java/org/sunbird/learner/actors/bulkupload/BaseBulkUploadActor.java b/actors/common/src/main/java/org/sunbird/learner/actors/bulkupload/BaseBulkUploadActor.java index b8a6c5e54..e8ca440d7 100644 --- a/actors/common/src/main/java/org/sunbird/learner/actors/bulkupload/BaseBulkUploadActor.java +++ b/actors/common/src/main/java/org/sunbird/learner/actors/bulkupload/BaseBulkUploadActor.java @@ -144,10 +144,8 @@ public BulkUploadProcess getBulkUploadProcessForFailedStatus( */ public CSVReader getCsvReader(byte[] byteArray, char seperator, char quoteChar, int lineNum) throws UnsupportedEncodingException { - InputStreamReader inputStreamReader = new InputStreamReader(new ByteArrayInputStream(byteArray), StandardCharsets.UTF_8); - // RFC4180Parser rfc4180Parser = new RFC4180ParserBuilder().build(); CSVReaderBuilder csvReaderBuilder = new CSVReaderBuilder(inputStreamReader); CSVReader csvReader = csvReaderBuilder.build(); return csvReader; diff --git a/actors/common/src/main/java/org/sunbird/learner/actors/bulkupload/UserBulkMigrationActor.java b/actors/common/src/main/java/org/sunbird/learner/actors/bulkupload/UserBulkMigrationActor.java new file mode 100644 index 000000000..baeb25fc9 --- /dev/null +++ b/actors/common/src/main/java/org/sunbird/learner/actors/bulkupload/UserBulkMigrationActor.java @@ -0,0 +1,368 @@ +package org.sunbird.learner.actors.bulkupload; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Iterables; +import com.mchange.v1.util.ArrayUtils; +import com.opencsv.CSVReader; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.sunbird.actor.router.ActorConfig; +import org.sunbird.actorutil.systemsettings.SystemSettingClient; +import org.sunbird.actorutil.systemsettings.impl.SystemSettingClientImpl; +import org.sunbird.bean.MigrationUser; +import org.sunbird.bean.ShadowUserUpload; +import org.sunbird.cassandra.CassandraOperation; +import org.sunbird.common.exception.ProjectCommonException; +import org.sunbird.common.models.response.Response; +import org.sunbird.common.models.util.*; +import org.sunbird.common.request.ExecutionContext; +import org.sunbird.common.request.Request; +import org.sunbird.common.responsecode.ResponseCode; +import org.sunbird.helper.ServiceFactory; +import org.sunbird.learner.actors.bulkupload.model.BulkMigrationUser; +import org.sunbird.learner.util.Util; +import org.sunbird.models.systemsetting.SystemSetting; +import org.sunbird.telemetry.util.TelemetryUtil; + +import java.io.IOException; +import java.sql.Timestamp; +import java.util.*; + + +/** + * @author anmolgupta + */ +@ActorConfig( + tasks = {"userBulkMigration"}, + asyncTasks = {} +) +public class UserBulkMigrationActor extends BaseBulkUploadActor { + private SystemSettingClient systemSettingClient = new SystemSettingClientImpl(); + private CassandraOperation cassandraOperation = ServiceFactory.getInstance(); + private static CSVReader csvReader; + public static final int RETRY_COUNT=2; + public static final String USER_BULK_MIGRATION_FIELD="shadowdbmandatorycolumn"; + private Util.DbInfo dbInfo = Util.dbInfoMap.get(JsonKey.BULK_OP_DB); + private Util.DbInfo usrDbInfo = Util.dbInfoMap.get(JsonKey.USER_DB); + private static ObjectMapper mapper=new ObjectMapper(); + private static SystemSetting systemSetting; + public static final String SHADOW_USER_UPLOAD="ShadowUserUpload"; + @Override + public void onReceive(Request request) throws Throwable { + Util.initializeContext(request, SHADOW_USER_UPLOAD); + ExecutionContext.setRequestId(request.getRequestId()); + String operation = request.getOperation(); + request.setContext( ExecutionContext.getCurrent().getRequestContext()); + if (operation.equalsIgnoreCase(BulkUploadActorOperation.USER_BULK_MIGRATION.getValue())) { + uploadCsv(request); + } else { + onReceiveUnsupportedOperation("userBulkMigration"); + } + } + + private void uploadCsv(Request request) throws IOException { + Map req = (Map) request.getRequest().get(JsonKey.DATA); + systemSetting = + systemSettingClient.getSystemSettingByField( + getActorRef(ActorOperations.GET_SYSTEM_SETTING.getValue()), + USER_BULK_MIGRATION_FIELD); + processCsvBytes(req,request); + } + + private void processCsvBytes(Mapdata,Request request) throws IOException { + Mapvalues= mapper.readValue(systemSetting.getValue(),Map.class); + Map targetObject = null; + List> correlatedObject = new ArrayList<>(); + String processId = ProjectUtil.getUniqueIdFromTimestamp(1); + long validationStartTime =System.currentTimeMillis(); + String userId=getCreatedBy(request); + Mapresult=getUserById(userId); + String channel=getChannel(result); + String rootOrgId=getRootOrgId(result); + ListmigrationUserList=getMigrationUsers(channel,processId,(byte[])data.get(JsonKey.FILE),values); + ProjectLogger.log("UserBulkMigrationActor:processRecord: time taken to validate records of size ".concat(migrationUserList.size()+"")+"is(ms): ".concat((System.currentTimeMillis()-validationStartTime)+""),LoggerEnum.INFO.name()); + request.getRequest().put(JsonKey.ROOT_ORG_ID,rootOrgId); + BulkMigrationUser migrationUser=prepareRecord(request,processId,migrationUserList); + ProjectLogger.log("UserBulkMigrationActor:processRecord:processing record for number of users ".concat(migrationUserList.size()+""),LoggerEnum.INFO.name()); + insertRecord(migrationUser); + TelemetryUtil.generateCorrelatedObject(processId, JsonKey.PROCESS_ID, null, correlatedObject); + TelemetryUtil.generateCorrelatedObject(migrationUser.getTaskCount()+"", JsonKey.TASK_COUNT, null, correlatedObject); + targetObject = + TelemetryUtil.generateTargetObject( + processId, StringUtils.capitalize(JsonKey.MIGRATION_USER_OBJECT), SHADOW_USER_UPLOAD, null); + TelemetryUtil.telemetryProcessingCall(mapper.convertValue(migrationUser,Map.class), targetObject, correlatedObject); + } + + private void insertRecord(BulkMigrationUser bulkMigrationUser){ + long insertStartTime=System.currentTimeMillis(); + ProjectLogger.log("UserBulkMigrationActor:insertRecord:record started inserting with ".concat(bulkMigrationUser.getId()+""),LoggerEnum.INFO.name()); + Maprecord=mapper.convertValue(bulkMigrationUser,Map.class); + long createdOn=System.currentTimeMillis(); + record.put(JsonKey.CREATED_ON,new Timestamp(createdOn)); + record.put(JsonKey.LAST_UPDATED_ON,new Timestamp(createdOn)); + Response response=cassandraOperation.insertRecord(dbInfo.getKeySpace(), dbInfo.getTableName(), record); + response.put(JsonKey.PROCESS_ID,bulkMigrationUser.getId()); + ProjectLogger.log("UserBulkMigrationActor:insertRecord:time taken by cassandra to insert record of size ".concat(record.size()+"")+"is(ms):".concat((System.currentTimeMillis()-insertStartTime)+"")); + sender().tell(response,self()); + } + private BulkMigrationUser prepareRecord(Request request,String processID,ListmigrationUserList){ + try { + String decryptedData=mapper.writeValueAsString(migrationUserList); + BulkMigrationUser migrationUser=new BulkMigrationUser.BulkMigrationUserBuilder(processID,decryptedData) + .setObjectType(JsonKey.MIGRATION_USER_OBJECT) + .setUploadedDate(ProjectUtil.getFormattedDate()) + .setStatus(ProjectUtil.BulkProcessStatus.NEW.getValue()) + .setRetryCount(RETRY_COUNT) + .setTaskCount(migrationUserList.size()) + .setCreatedBy(getCreatedBy(request)) + .setUploadedBy(getCreatedBy(request)) + .setOrganisationId((String)request.getRequest().get(JsonKey.ROOT_ORG_ID)) + .setTelemetryContext(getContextMap(processID,request)) + .build(); + return migrationUser; + }catch (Exception e){ + e.printStackTrace(); + ProjectLogger.log("UserBulkMigrationActor:prepareRecord:error occurred while getting preparing record with processId".concat(processID+""),LoggerEnum.ERROR.name()); + throw new ProjectCommonException( + ResponseCode.SERVER_ERROR.getErrorCode(), + ResponseCode.SERVER_ERROR.getErrorMessage(), + ResponseCode.SERVER_ERROR.getResponseCode()); + } + } + + private MapgetContextMap(String processId,Request request){ + Map contextMap = (Map)request.getContext(); + ProjectLogger.log("UserBulkMigrationActor:getContextMap:started preparing record for processId:"+processId+"with request context:"+contextMap,LoggerEnum.INFO.name()); + contextMap.put(JsonKey.ACTOR_TYPE, StringUtils.capitalize(JsonKey.SYSTEM)); + contextMap.put(JsonKey.ACTOR_ID,ProjectUtil.getUniqueIdFromTimestamp(0)); + Iterables.removeIf(contextMap.values(),value->StringUtils.isBlank(value)); + return contextMap; + } + + private String getCreatedBy(Request request){ + Mapdata=(Map) request.getRequest().get(JsonKey.DATA); + return MapUtils.isNotEmpty(data)?data.get(JsonKey.CREATED_BY):null; + } + + private List getMigrationUsers(String channel,String processId,byte[] fileData,MapfieldsMap){ + Map> columnsMap = (Map>) fieldsMap.get(JsonKey.FILE_TYPE_CSV); + List csvData=readCsv(fileData); + ListcsvHeaders=getCsvHeadersAsList(csvData); + ListmandatoryHeaders=columnsMap.get(JsonKey.MANDATORY_FIELDS); + ListsupportedHeaders=columnsMap.get(JsonKey.SUPPORTED_COlUMNS); + mandatoryHeaders.replaceAll(String::toLowerCase); + supportedHeaders.replaceAll(String::toLowerCase); + checkCsvHeader(csvHeaders,mandatoryHeaders,supportedHeaders); + ListmappedCsvHeaders=mapCsvColumn(csvHeaders); + ListmigrationUserList=parseCsvRows(channel,getCsvRowsAsList(csvData),mappedCsvHeaders); + ShadowUserUpload migration = new ShadowUserUpload.ShadowUserUploadBuilder() + .setHeaders(csvHeaders) + .setMappedHeaders(mappedCsvHeaders) + .setProcessId(processId) + .setFileData(fileData) + .setFileSize(fileData.length+"") + .setMandatoryFields(columnsMap.get(JsonKey.MANDATORY_FIELDS)) + .setSupportedFields(columnsMap.get(JsonKey.SUPPORTED_COlUMNS)) + .setValues(migrationUserList) + .validate(); + ProjectLogger.log("UserBulkMigrationActor:validateRequestAndReturnMigrationUsers: the migration object formed ".concat(migration.toString()),LoggerEnum.INFO.name()); + return migrationUserList; + } + + private List readCsv(byte[] fileData){ + Listvalues=new ArrayList<>(); + try { + csvReader = getCsvReader(fileData, ',', '"', 0); + ProjectLogger.log("UserBulkMigrationActor:readCsv:csvReader initialized ".concat(csvReader.toString()),LoggerEnum.INFO.name()); + values=csvReader.readAll(); + } + catch (Exception ex) { + ProjectLogger.log("UserBulkMigrationActor:readCsv:error occurred while getting csvReader",LoggerEnum.ERROR.name()); + throw new ProjectCommonException( + ResponseCode.SERVER_ERROR.getErrorCode(), + ResponseCode.SERVER_ERROR.getErrorMessage(), + ResponseCode.SERVER_ERROR.getResponseCode()); + } finally { + IOUtils.closeQuietly(csvReader); + } + return values; + } + + private List getCsvHeadersAsList(ListcsvData){ + Listheaders=new ArrayList<>(); + int CSV_COLUMN_NAMES=0; + if(null==csvData || csvData.isEmpty()){ + throw new ProjectCommonException( + ResponseCode.blankCsvData.getErrorCode(), + ResponseCode.blankCsvData.getErrorMessage(), + ResponseCode.CLIENT_ERROR.getResponseCode()); + } + headers.addAll(Arrays.asList(csvData.get(CSV_COLUMN_NAMES))); + headers.replaceAll(String::toLowerCase); + return headers; + } + private List getCsvRowsAsList(ListcsvData){ + return csvData.subList(1,csvData.size()); + } + + private List mapCsvColumn(List csvColumns){ + List mappedColumns=new ArrayList<>(); + csvColumns.forEach(column->{ + if(column.equalsIgnoreCase(JsonKey.EMAIL)){ + mappedColumns.add(column); + } + if (column.equalsIgnoreCase(JsonKey.PHONE)) { + mappedColumns.add(column); + } + if(column.equalsIgnoreCase(JsonKey.EXTERNAL_USER_ID)) + { + mappedColumns.add(JsonKey.USER_EXTERNAL_ID); + } + if(column.equalsIgnoreCase(JsonKey.EXTERNAL_ORG_ID)){ + mappedColumns.add(JsonKey.ORG_EXTERNAL_ID); + } + if(column.equalsIgnoreCase(JsonKey.NAME)){ + mappedColumns.add(JsonKey.FIRST_NAME); + } + if(column.equalsIgnoreCase(JsonKey.INPUT_STATUS)){ + mappedColumns.add(column); + } + }); + return mappedColumns; + + } + + private List parseCsvRows(String channel,List values,ListmappedHeaders){ + List migrationUserList=new ArrayList<>(); + values.stream().forEach(row->{ + int index=values.indexOf(row); + MigrationUser migrationUser=new MigrationUser(); + for(int i=0;imappedHeaders.size()){ + throw new ProjectCommonException( + ResponseCode.errorUnsupportedField.getErrorCode(), + ResponseCode.errorUnsupportedField.getErrorMessage(), + ResponseCode.CLIENT_ERROR.getResponseCode(), + "Invalid provided ROW:"+(index+1)); + } + String columnName=getColumnNameByIndex(mappedHeaders,i); + setFieldToMigrationUserObject(migrationUser,columnName,trimValue(row[i])); + } + //channel to be added here + migrationUser.setChannel(channel); + migrationUserList.add(migrationUser); + }); + return migrationUserList; + } + + + private String trimValue(String value){ + if(StringUtils.isNotBlank(value)){ + return value.trim(); + } + return value; + } + + private void setFieldToMigrationUserObject(MigrationUser migrationUser,String columnAttribute,Object value){ + + if(columnAttribute.equalsIgnoreCase(JsonKey.EMAIL)){ + String email=(String)value; + migrationUser.setEmail(email); + } + if(columnAttribute.equalsIgnoreCase(JsonKey.PHONE)){ + String phone=(String)value; + migrationUser.setPhone(phone); + } + if(columnAttribute.equalsIgnoreCase(JsonKey.ORG_EXTERNAL_ID)){ + migrationUser.setOrgExternalId((String)value); + } + if(columnAttribute.equalsIgnoreCase(JsonKey.USER_EXTERNAL_ID)){ + migrationUser.setUserExternalId((String)value); + } + + if(columnAttribute.equalsIgnoreCase(JsonKey.FIRST_NAME)){ + migrationUser.setName(StringUtils.trim((String)value)); + } + if(columnAttribute.equalsIgnoreCase(JsonKey.INPUT_STATUS)) + { + migrationUser.setInputStatus((String)value); + } + } + private String getColumnNameByIndex(ListmappedHeaders,int index){ + return mappedHeaders.get(index); + } + + + /** + * in bulk_upload_process db user will have channel of admin users + * @param result + * @return channel + */ + private String getChannel(Map result){ + String channel = (String) result.get(JsonKey.CHANNEL); + ProjectLogger.log("UserBulkMigrationActor:getChannel: the channel of admin user ".concat(channel + ""), LoggerEnum.INFO.name()); + return channel; + } + /** + * in bulk_upload_process db organisationId will be of user. + * @param result + * @return rootOrgId + */ + private String getRootOrgId( Map result){ + String rootOrgId = (String) result.get(JsonKey.ROOT_ORG_ID); + ProjectLogger.log("UserBulkMigrationActor:getRootOrgId:the root org id of admin user ".concat(rootOrgId + ""), LoggerEnum.INFO.name()); + return rootOrgId; + } + + + /** + * this method will fetch user record with userId from cassandra + * @param userId + * @return result + */ + private Map getUserById(String userId){ + Response response=cassandraOperation.getRecordById(usrDbInfo.getKeySpace(),usrDbInfo.getTableName(),userId); + if(((List)response.getResult().get(JsonKey.RESPONSE)).isEmpty()) { + throw new ProjectCommonException( + ResponseCode.userNotFound.getErrorCode(), + ResponseCode.userNotFound.getErrorMessage(), + ResponseCode.CLIENT_ERROR.getResponseCode()); + } + Mapresult=((Map)((List)response.getResult().get(JsonKey.RESPONSE)).get(0)); + return result; + } + + private void checkCsvHeader(ListcsvHeaders,ListmandatoryHeaders,ListsupportedHeaders){ + checkMandatoryColumns(csvHeaders,mandatoryHeaders); + checkSupportedColumns(csvHeaders,supportedHeaders); + } + private void checkMandatoryColumns(ListcsvHeaders,ListmandatoryHeaders){ + ProjectLogger.log("UserBulkMigrationRequestValidator:checkMandatoryColumns:mandatory columns got "+ mandatoryHeaders,LoggerEnum.INFO.name()); + mandatoryHeaders.forEach( + column->{ + if(!csvHeaders.contains(column)){ + ProjectLogger.log("UserBulkMigrationRequestValidator:mandatoryColumns: mandatory column is not present".concat(column+""), LoggerEnum.ERROR.name()); + throw new ProjectCommonException( + ResponseCode.mandatoryParamsMissing.getErrorCode(), + ResponseCode.mandatoryParamsMissing.getErrorMessage(), + ResponseCode.CLIENT_ERROR.getResponseCode(), + column); + } + } + ); + } + + private void checkSupportedColumns(ListcsvHeaders,ListsupportedHeaders){ + ProjectLogger.log("UserBulkMigrationRequestValidator:checkSupportedColumns:mandatory columns got "+ supportedHeaders,LoggerEnum.INFO.name()); + csvHeaders.forEach(suppColumn->{ + if(!supportedHeaders.contains(suppColumn)){ + ProjectLogger.log("UserBulkMigrationRequestValidator:supportedColumns: supported column is not present".concat(suppColumn+""), LoggerEnum.ERROR.name()); + throw new ProjectCommonException( + ResponseCode.errorUnsupportedField.getErrorCode(), + ResponseCode.errorUnsupportedField.getErrorMessage(), + ResponseCode.CLIENT_ERROR.getResponseCode(), + "Invalid provided column:".concat(suppColumn).concat(":supported headers are:").concat(ArrayUtils.stringifyContents(supportedHeaders.toArray()))); + } + }); +}} diff --git a/actors/common/src/main/java/org/sunbird/learner/actors/bulkupload/UserBulkUploadBackgroundJobActor.java b/actors/common/src/main/java/org/sunbird/learner/actors/bulkupload/UserBulkUploadBackgroundJobActor.java index 199f02e8c..5bb4306af 100644 --- a/actors/common/src/main/java/org/sunbird/learner/actors/bulkupload/UserBulkUploadBackgroundJobActor.java +++ b/actors/common/src/main/java/org/sunbird/learner/actors/bulkupload/UserBulkUploadBackgroundJobActor.java @@ -9,6 +9,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; + import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.sunbird.actor.router.ActorConfig; @@ -33,7 +34,6 @@ import org.sunbird.learner.util.UserUtility; import org.sunbird.learner.util.Util; import org.sunbird.models.organisation.Organisation; -import org.sunbird.models.user.User; import org.sunbird.validator.user.UserBulkUploadRequestValidator; @ActorConfig( @@ -206,28 +206,25 @@ private void processUser(BulkUploadProcessTask task, String organisationId, Stri return; } - User user = mapper.convertValue(userMap, User.class); - user.setId((String) userMap.get(JsonKey.USER_ID)); String orgName = ""; if (null != organisation) { orgName = organisation.getOrgName(); } - if (StringUtils.isNotEmpty(user.getPhone())) { - user.setPhoneVerified(true); + if(StringUtils.isNotEmpty((String) userMap.get(JsonKey.PHONE))) { + userMap.put(JsonKey.PHONE_VERIFIED, true); } - - if (StringUtils.isNotEmpty(user.getEmail())) { - user.setEmailVerified(true); + if(StringUtils.isNotEmpty((String) userMap.get(JsonKey.EMAIL))) { + userMap.put(JsonKey.EMAIL_VERIFIED, true); } - - if (StringUtils.isEmpty(user.getId())) { - user.setCreatedBy(uploadedBy); - user.setRootOrgId(organisationId); - callCreateUser(user, task, orgName); + String userId = (String) userMap.get(JsonKey.USER_ID); + if (StringUtils.isEmpty(userId)) { + userMap.put(JsonKey.CREATED_BY, uploadedBy); + userMap.put(JsonKey.ROOT_ORG_ID, organisationId); + callCreateUser(userMap, task, orgName); } else { - user.setUpdatedBy(uploadedBy); - callUpdateUser(user, task, orgName); + userMap.put(JsonKey.UPDATED_BY, uploadedBy); + callUpdateUser(userMap, task, orgName); } } catch (Exception e) { ProjectLogger.log("Error in process user", data, e); @@ -236,20 +233,19 @@ private void processUser(BulkUploadProcessTask task, String organisationId, Stri } @SuppressWarnings("unchecked") - private void callCreateUser(User user, BulkUploadProcessTask task, String orgName) + private void callCreateUser(Map user, BulkUploadProcessTask task, String orgName) throws JsonProcessingException { ProjectLogger.log("UserBulkUploadBackgroundJobActor: callCreateUser called", LoggerEnum.INFO); - Map row = mapper.convertValue(user, Map.class); String userId; try { - userId = userClient.createUser(getActorRef(ActorOperations.CREATE_USER.getValue()), row); + userId = userClient.createUser(getActorRef(ActorOperations.CREATE_USER.getValue()), user); } catch (Exception ex) { ProjectLogger.log( "UserBulkUploadBackgroundJobActor:callCreateUser: Exception occurred with error message = " + ex.getMessage(), LoggerEnum.INFO); setTaskStatus( - task, ProjectUtil.BulkProcessStatus.FAILED, ex.getMessage(), row, JsonKey.CREATE); + task, ProjectUtil.BulkProcessStatus.FAILED, ex.getMessage(), user, JsonKey.CREATE); return; } @@ -260,36 +256,34 @@ private void callCreateUser(User user, BulkUploadProcessTask task, String orgNam task, ProjectUtil.BulkProcessStatus.FAILED, ResponseCode.internalError.getErrorMessage(), - row, + user, JsonKey.CREATE); } else { - row.put(JsonKey.ID, userId); - row.put(JsonKey.ORG_NAME, orgName); - setSuccessTaskStatus(task, ProjectUtil.BulkProcessStatus.COMPLETED, row, JsonKey.CREATE); + user.put(JsonKey.ID, userId); + user.put(JsonKey.ORG_NAME, orgName); + setSuccessTaskStatus(task, ProjectUtil.BulkProcessStatus.COMPLETED, user, JsonKey.CREATE); } } @SuppressWarnings("unchecked") - private void callUpdateUser(User user, BulkUploadProcessTask task, String orgName) + private void callUpdateUser(Map user, BulkUploadProcessTask task, String orgName) throws JsonProcessingException { ProjectLogger.log("UserBulkUploadBackgroundJobActor: callUpdateUser called", LoggerEnum.INFO); - Map row = mapper.convertValue(user, Map.class); try { - row.put(JsonKey.USER_ID, user.getId()); - row.put(JsonKey.ORG_NAME, orgName); - userClient.updateUser(getActorRef(ActorOperations.UPDATE_USER.getValue()), row); + user.put(JsonKey.ORG_NAME, orgName); + userClient.updateUser(getActorRef(ActorOperations.UPDATE_USER.getValue()), user); } catch (Exception ex) { ProjectLogger.log( "UserBulkUploadBackgroundJobActor:callUpdateUser: Exception occurred with error message = " + ex.getMessage(), LoggerEnum.INFO); - row.put(JsonKey.ERROR_MSG, ex.getMessage()); + user.put(JsonKey.ERROR_MSG, ex.getMessage()); setTaskStatus( - task, ProjectUtil.BulkProcessStatus.FAILED, ex.getMessage(), row, JsonKey.UPDATE); + task, ProjectUtil.BulkProcessStatus.FAILED, ex.getMessage(), user, JsonKey.UPDATE); } if (task.getStatus() != ProjectUtil.BulkProcessStatus.FAILED.getValue()) { - task.setData(mapper.writeValueAsString(row)); - setSuccessTaskStatus(task, ProjectUtil.BulkProcessStatus.COMPLETED, row, JsonKey.UPDATE); + task.setData(mapper.writeValueAsString(user)); + setSuccessTaskStatus(task, ProjectUtil.BulkProcessStatus.COMPLETED, user, JsonKey.UPDATE); } } diff --git a/actors/common/src/main/java/org/sunbird/learner/actors/bulkupload/model/BulkMigrationUser.java b/actors/common/src/main/java/org/sunbird/learner/actors/bulkupload/model/BulkMigrationUser.java new file mode 100644 index 000000000..20b29a072 --- /dev/null +++ b/actors/common/src/main/java/org/sunbird/learner/actors/bulkupload/model/BulkMigrationUser.java @@ -0,0 +1,283 @@ +package org.sunbird.learner.actors.bulkupload.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import org.codehaus.jackson.annotate.JsonIgnore; +import org.sunbird.common.exception.ProjectCommonException; +import org.sunbird.common.models.util.LoggerEnum; +import org.sunbird.common.models.util.ProjectLogger; +import org.sunbird.common.models.util.datasecurity.DecryptionService; +import org.sunbird.common.models.util.datasecurity.EncryptionService; +import org.sunbird.common.responsecode.ResponseCode; + +import java.sql.Timestamp; +import java.util.Map; + +/** + * @author anmolgupta + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class BulkMigrationUser { + private static final long serialVersionUID = 1L; + private String id; + private String data; + private String failureResult; + private String objectType; + private String organisationId; + private String processEndTime; + private String processStartTime; + private Integer retryCount; + private Integer status; + private String successResult; + private String uploadedBy; + private String uploadedDate; + private Integer taskCount; + private String createdBy; + private Timestamp createdOn; + private Timestamp lastUpdatedOn; + private String storageDetails; + private Map telemetryContext; + + + + public BulkMigrationUser(BulkMigrationUserBuilder builder) { + this.id = builder.id; + this.data = builder.data; + this.failureResult = builder.failureResult; + this.objectType = builder.objectType; + this.organisationId = builder.organisationId; + this.processEndTime = builder.processEndTime; + this.processStartTime = builder.processStartTime; + this.retryCount = builder.retryCount; + this.status = builder.status; + this.successResult = builder.successResult; + this.uploadedBy = builder.uploadedBy; + this.uploadedDate = builder.uploadedDate; + this.taskCount = builder.taskCount; + this.createdBy = builder.createdBy; + this.createdOn = builder.createdOn; + this.lastUpdatedOn = builder.lastUpdatedOn; + this.storageDetails = builder.storageDetails; + this.telemetryContext=builder.telemetryContext; + } + + public BulkMigrationUser() { + + } + + public String getId() { + return id; + } + + public String getData() { + return data; + } + + public String getFailureResult() { + return failureResult; + } + + public String getObjectType() { + return objectType; + } + + public String getOrganisationId() { + return organisationId; + } + + public String getProcessEndTime() { + return processEndTime; + } + + public String getProcessStartTime() { + return processStartTime; + } + + public Integer getRetryCount() { + return retryCount; + } + + public Integer getStatus() { + return status; + } + + public String getSuccessResult() { + return successResult; + } + + public String getUploadedBy() { + return uploadedBy; + } + + public String getUploadedDate() { + return uploadedDate; + } + + public Integer getTaskCount() { + return taskCount; + } + + public String getCreatedBy() { + return createdBy; + } + + public Timestamp getCreatedOn() { + return createdOn; + } + + public Timestamp getLastUpdatedOn() { + return lastUpdatedOn; + } + + public String getStorageDetails() { + return storageDetails; + } + + + public Map getTelemetryContext() { + return telemetryContext; + } + + public static class BulkMigrationUserBuilder { + private EncryptionService encryptionService = org.sunbird.common.models.util.datasecurity.impl.ServiceFactory.getEncryptionServiceInstance(null); + private String id; + private String data; + private String failureResult; + private String objectType; + private String organisationId; + private String processEndTime; + private String processStartTime; + private Integer retryCount; + private Integer status; + private String successResult; + private String uploadedBy; + private String uploadedDate; + private Integer taskCount; + private String createdBy; + private Timestamp createdOn; + private Timestamp lastUpdatedOn; + private String storageDetails; + private Map telemetryContext; + + public BulkMigrationUserBuilder setTelemetryContext(Map telemetryContext) { + this.telemetryContext = telemetryContext; + return this; + } + + public BulkMigrationUserBuilder(String id, String data) { + this.id = id; + this.data = encryptData(data); + } + + public BulkMigrationUserBuilder setFailureResult(String failureResult) { + this.failureResult = failureResult; + return this; + } + + public BulkMigrationUserBuilder setObjectType(String objectType) { + this.objectType = objectType; + return this; + + } + + public BulkMigrationUserBuilder setOrganisationId(String organisationId) { + this.organisationId = organisationId; + return this; + + } + + public BulkMigrationUserBuilder setProcessEndTime(String processEndTime) { + this.processEndTime = processEndTime; + return this; + + } + + public BulkMigrationUserBuilder setProcessStartTime(String processStartTime) { + this.processStartTime = processStartTime; + return this; + + } + + public BulkMigrationUserBuilder setRetryCount(Integer retryCount) { + this.retryCount = retryCount; + return this; + + } + + public BulkMigrationUserBuilder setStatus(Integer status) { + this.status = status; + return this; + + } + + public BulkMigrationUserBuilder setSuccessResult(String successResult) { + this.successResult = successResult; + return this; + + } + + public BulkMigrationUserBuilder setUploadedBy(String uploadedBy) { + this.uploadedBy = uploadedBy; + return this; + + } + + public BulkMigrationUserBuilder setUploadedDate(String uploadedDate) { + this.uploadedDate = uploadedDate; + return this; + + } + + public BulkMigrationUserBuilder setTaskCount(Integer taskCount) { + this.taskCount = taskCount; + return this; + + } + + public BulkMigrationUserBuilder setCreatedBy(String createdBy) { + this.createdBy = createdBy; + return this; + + } + + public BulkMigrationUserBuilder setCreatedOn(Timestamp createdOn) { + this.createdOn = createdOn; + return this; + + } + + public BulkMigrationUserBuilder setLastUpdatedOn(Timestamp lastUpdatedOn) { + this.lastUpdatedOn = lastUpdatedOn; + return this; + + } + + public BulkMigrationUserBuilder setStorageDetails(String storageDetails) { + this.storageDetails = storageDetails; + return this; + } + + private String encryptData(String decryptedData) { + long encStartTime=System.currentTimeMillis(); + try { + String encryptedData= encryptionService.encryptData(decryptedData); + ProjectLogger.log("BulkMigrationUser:encryptData:TIME TAKEN TO ENCRYPT DATA in(ms):".concat((System.currentTimeMillis()-encStartTime)+""),LoggerEnum.INFO.name()); + return encryptedData; + } catch (Exception e) { + ProjectLogger.log("BulkMigrationUser:encryptData:error occurred while encrypting data", LoggerEnum.ERROR.name()); + throw new ProjectCommonException( + ResponseCode.SERVER_ERROR.getErrorCode(), + ResponseCode.userDataEncryptionError.getErrorMessage(), + ResponseCode.userDataEncryptionError.getResponseCode()); + } + } + + public BulkMigrationUser build() { + BulkMigrationUser migrationUser = new BulkMigrationUser(this); + return migrationUser; + } + } + +} + diff --git a/actors/common/src/main/java/org/sunbird/learner/actors/otp/OTPActor.java b/actors/common/src/main/java/org/sunbird/learner/actors/otp/OTPActor.java index 9a7748e63..f6623b2ee 100644 --- a/actors/common/src/main/java/org/sunbird/learner/actors/otp/OTPActor.java +++ b/actors/common/src/main/java/org/sunbird/learner/actors/otp/OTPActor.java @@ -1,143 +1,145 @@ -package org.sunbird.learner.actors.otp; - -import java.util.Map; -import org.apache.commons.collections.MapUtils; -import org.apache.commons.lang3.StringUtils; -import org.sunbird.actor.core.BaseActor; -import org.sunbird.actor.router.ActorConfig; -import org.sunbird.common.exception.ProjectCommonException; -import org.sunbird.common.models.response.Response; -import org.sunbird.common.models.util.ActorOperations; -import org.sunbird.common.models.util.JsonKey; -import org.sunbird.common.models.util.LoggerEnum; -import org.sunbird.common.models.util.ProjectLogger; -import org.sunbird.common.request.Request; -import org.sunbird.common.responsecode.ResponseCode; -import org.sunbird.learner.actors.otp.service.OTPService; -import org.sunbird.learner.util.OTPUtil; -import org.sunbird.ratelimit.limiter.OtpRateLimiter; -import org.sunbird.ratelimit.limiter.RateLimiter; -import org.sunbird.ratelimit.service.RateLimitService; -import org.sunbird.ratelimit.service.RateLimitServiceImpl; - -@ActorConfig( - tasks = {"generateOTP", "verifyOTP"}, - asyncTasks = {} -) -public class OTPActor extends BaseActor { - - private OTPService otpService = new OTPService(); - private RateLimitService rateLimitService = new RateLimitServiceImpl(); - - @Override - public void onReceive(Request request) throws Throwable { - if (ActorOperations.GENERATE_OTP.getValue().equals(request.getOperation())) { - generateOTP(request); - } else if (ActorOperations.VERIFY_OTP.getValue().equals(request.getOperation())) { - verifyOTP(request); - } else { - onReceiveUnsupportedOperation("OTPActor"); - } - } - - private void generateOTP(Request request) { - String type = (String) request.getRequest().get(JsonKey.TYPE); - String key = getKey(type, request); - - String userId = (String) request.getRequest().get(JsonKey.USER_ID); - if (StringUtils.isNotBlank(userId)) { - key = OTPUtil.getEmailPhoneByUserId(userId, type); - type = getType(type); - } - - rateLimitService.throttleByKey( - key, new RateLimiter[] {OtpRateLimiter.HOUR, OtpRateLimiter.DAY}); - - String otp = null; - Map details = otpService.getOTPDetails(type, key); - if (MapUtils.isEmpty(details)) { - otp = OTPUtil.generateOTP(); - ProjectLogger.log("OTPActor:generateOTP: Key = " + key + " OTP = " + otp, LoggerEnum.DEBUG); - otpService.insertOTPDetails(type, key, otp); - } else { - otp = (String) details.get(JsonKey.OTP); - } - - Response response = new Response(); - response.put(JsonKey.RESPONSE, JsonKey.SUCCESS); - sender().tell(response, self()); - - sendOTP(request, otp, key); - } - - private String getType(String type) { - switch (type) { - case JsonKey.PREV_USED_EMAIL: - return JsonKey.EMAIL; - case JsonKey.PREV_USED_PHONE: - return JsonKey.PHONE; - case JsonKey.EMAIL: - return JsonKey.EMAIL; - case JsonKey.PHONE: - return JsonKey.PHONE; - default: - return null; - } - } - - private void verifyOTP(Request request) { - String type = (String) request.getRequest().get(JsonKey.TYPE); - String key = getKey(type, request); - String otpInRequest = (String) request.getRequest().get(JsonKey.OTP); - - String userId = (String) request.getRequest().get(JsonKey.USER_ID); - if (StringUtils.isNotBlank(userId)) { - key = OTPUtil.getEmailPhoneByUserId(userId, type); - type = getType(type); - } - - Map otpDetails = otpService.getOTPDetails(type, key); - - if (MapUtils.isEmpty(otpDetails)) { - ProjectLogger.log( - "OTPActor:verifyOTP: Details not found for type = " + type + " key = " + key, - LoggerEnum.DEBUG); - ProjectCommonException.throwClientErrorException(ResponseCode.errorInvalidOTP); - } - - String otpInDB = (String) otpDetails.get(JsonKey.OTP); - - if (otpInDB == null || otpInRequest == null || !otpInRequest.equals(otpInDB)) { - ProjectLogger.log( - "OTPActor:verifyOTP: OTP mismatch otpInRequest = " - + otpInRequest - + " otpInDB = " - + otpInDB, - LoggerEnum.DEBUG); - ProjectCommonException.throwClientErrorException(ResponseCode.errorInvalidOTP); - } - - Response response = new Response(); - response.put(JsonKey.RESPONSE, JsonKey.SUCCESS); - sender().tell(response, self()); - } - - private void sendOTP(Request request, String otp, String key) { - Request sendOtpRequest = new Request(); - sendOtpRequest.getRequest().putAll(request.getRequest()); - sendOtpRequest.getRequest().put(JsonKey.KEY, key); - sendOtpRequest.getRequest().put(JsonKey.OTP, otp); - sendOtpRequest.setOperation(ActorOperations.SEND_OTP.getValue()); - - // Sent OTP via email or sms - tellToAnother(sendOtpRequest); - } - - private String getKey(String type, Request request) { - String key = (String) request.getRequest().get(JsonKey.KEY); - if (JsonKey.EMAIL.equalsIgnoreCase(type) && key != null) { - return key.toLowerCase(); - } - return key; - } -} +package org.sunbird.learner.actors.otp; + +import java.util.Map; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.StringUtils; +import org.sunbird.actor.core.BaseActor; +import org.sunbird.actor.router.ActorConfig; +import org.sunbird.common.exception.ProjectCommonException; +import org.sunbird.common.models.response.Response; +import org.sunbird.common.models.util.ActorOperations; +import org.sunbird.common.models.util.JsonKey; +import org.sunbird.common.models.util.LoggerEnum; +import org.sunbird.common.models.util.ProjectLogger; +import org.sunbird.common.request.Request; +import org.sunbird.common.responsecode.ResponseCode; +import org.sunbird.learner.actors.otp.service.OTPService; +import org.sunbird.learner.util.OTPUtil; +import org.sunbird.ratelimit.limiter.OtpRateLimiter; +import org.sunbird.ratelimit.limiter.RateLimiter; +import org.sunbird.ratelimit.service.RateLimitService; +import org.sunbird.ratelimit.service.RateLimitServiceImpl; + +@ActorConfig( + tasks = {"generateOTP", "verifyOTP"}, + asyncTasks = {} +) +public class OTPActor extends BaseActor { + + private OTPService otpService = new OTPService(); + private RateLimitService rateLimitService = new RateLimitServiceImpl(); + + @Override + public void onReceive(Request request) throws Throwable { + if (ActorOperations.GENERATE_OTP.getValue().equals(request.getOperation())) { + generateOTP(request); + } else if (ActorOperations.VERIFY_OTP.getValue().equals(request.getOperation())) { + verifyOTP(request); + } else { + onReceiveUnsupportedOperation("OTPActor"); + } + } + + private void generateOTP(Request request) { + String type = (String) request.getRequest().get(JsonKey.TYPE); + String key = getKey(type, request); + + String userId = (String) request.getRequest().get(JsonKey.USER_ID); + if (StringUtils.isNotBlank(userId)) { + key = OTPUtil.getEmailPhoneByUserId(userId, type); + type = getType(type); + } + + rateLimitService.throttleByKey( + key, new RateLimiter[] {OtpRateLimiter.HOUR, OtpRateLimiter.DAY}); + + String otp = null; + Map details = otpService.getOTPDetails(type, key); + if (MapUtils.isEmpty(details)) { + otp = OTPUtil.generateOTP(); + ProjectLogger.log("OTPActor:generateOTP: Key = " + key + " OTP = " + otp, LoggerEnum.DEBUG); + otpService.insertOTPDetails(type, key, otp); + } else { + otp = (String) details.get(JsonKey.OTP); + } + + Response response = new Response(); + response.put(JsonKey.RESPONSE, JsonKey.SUCCESS); + sender().tell(response, self()); + + sendOTP(request, otp, key); + } + + private String getType(String type) { + switch (type) { + case JsonKey.PREV_USED_EMAIL: + return JsonKey.EMAIL; + case JsonKey.PREV_USED_PHONE: + return JsonKey.PHONE; + case JsonKey.EMAIL: + return JsonKey.EMAIL; + case JsonKey.PHONE: + return JsonKey.PHONE; + case JsonKey.RECOVERY_EMAIL: + return JsonKey.EMAIL; + case JsonKey.RECOVERY_PHONE: + return JsonKey.PHONE; + default: + return null; + } + } + + private void verifyOTP(Request request) { + String type = (String) request.getRequest().get(JsonKey.TYPE); + String key = getKey(type, request); + String otpInRequest = (String) request.getRequest().get(JsonKey.OTP); + + String userId = (String) request.getRequest().get(JsonKey.USER_ID); + if (StringUtils.isNotBlank(userId)) { + key = OTPUtil.getEmailPhoneByUserId(userId, type); + type = getType(type); + } + + Map otpDetails = otpService.getOTPDetails(type, key); + if (MapUtils.isEmpty(otpDetails)) { + ProjectLogger.log( + "OTPActor:verifyOTP: Details not found for type = " + type + " key = " + key, + LoggerEnum.DEBUG); + ProjectCommonException.throwClientErrorException(ResponseCode.errorInvalidOTP); + } + otpService.deleteOtp(type,key); + String otpInDB = (String) otpDetails.get(JsonKey.OTP); + if (otpInDB == null || otpInRequest == null || !otpInRequest.equals(otpInDB)) { + ProjectLogger.log( + "OTPActor:verifyOTP: OTP mismatch otpInRequest = " + + otpInRequest + + " otpInDB = " + + otpInDB, + LoggerEnum.DEBUG); + ProjectCommonException.throwClientErrorException(ResponseCode.errorInvalidOTP); + } + + Response response = new Response(); + response.put(JsonKey.RESPONSE, JsonKey.SUCCESS); + sender().tell(response, self()); + } + + private void sendOTP(Request request, String otp, String key) { + Request sendOtpRequest = new Request(); + sendOtpRequest.getRequest().putAll(request.getRequest()); + sendOtpRequest.getRequest().put(JsonKey.KEY, key); + sendOtpRequest.getRequest().put(JsonKey.OTP, otp); + sendOtpRequest.setOperation(ActorOperations.SEND_OTP.getValue()); + + // Sent OTP via email or sms + tellToAnother(sendOtpRequest); + } + + private String getKey(String type, Request request) { + String key = (String) request.getRequest().get(JsonKey.KEY); + if (JsonKey.EMAIL.equalsIgnoreCase(type) && key != null) { + return key.toLowerCase(); + } + return key; + } +} diff --git a/actors/common/src/main/java/org/sunbird/learner/actors/otp/SendOTPActor.java b/actors/common/src/main/java/org/sunbird/learner/actors/otp/SendOTPActor.java index af7178914..7795501b8 100644 --- a/actors/common/src/main/java/org/sunbird/learner/actors/otp/SendOTPActor.java +++ b/actors/common/src/main/java/org/sunbird/learner/actors/otp/SendOTPActor.java @@ -1,68 +1,68 @@ -package org.sunbird.learner.actors.otp; - -import java.util.HashMap; -import java.util.Map; -import org.apache.commons.lang3.StringUtils; -import org.sunbird.actor.core.BaseActor; -import org.sunbird.actor.router.ActorConfig; -import org.sunbird.common.models.util.ActorOperations; -import org.sunbird.common.models.util.JsonKey; -import org.sunbird.common.models.util.LoggerEnum; -import org.sunbird.common.models.util.ProjectLogger; -import org.sunbird.common.request.Request; -import org.sunbird.learner.util.OTPUtil; - -@ActorConfig( - tasks = {}, - asyncTasks = {"sendOTP"} -) -public class SendOTPActor extends BaseActor { - public static final String RESET_PASSWORD = "resetPassword"; - - @Override - public void onReceive(Request request) throws Throwable { - - if (ActorOperations.SEND_OTP.getValue().equals(request.getOperation())) { - sendOTP(request); - } else { - onReceiveUnsupportedOperation("SendOTPActor"); - } - } - - private void sendOTP(Request request) { - String type = (String) request.getRequest().get(JsonKey.TYPE); - String key = (String) request.getRequest().get(JsonKey.KEY); - String otp = (String) request.getRequest().get(JsonKey.OTP); - if (JsonKey.EMAIL.equalsIgnoreCase(type) || JsonKey.PREV_USED_EMAIL.equalsIgnoreCase(type)) { - String userId = (String) request.get(JsonKey.USER_ID); - ProjectLogger.log( - "SendOTPActor:sendOTP userId found for send otp " + userId, LoggerEnum.INFO.name()); - sendOTPViaEmail(key, otp, userId); - } else if (JsonKey.PHONE.equalsIgnoreCase(type) - || JsonKey.PREV_USED_PHONE.equalsIgnoreCase(type)) { - sendOTPViaSMS(key, otp); - } - } - - private void sendOTPViaEmail(String key, String otp, String otpType) { - Map emailTemplateMap = new HashMap<>(); - emailTemplateMap.put(JsonKey.EMAIL, key); - emailTemplateMap.put(JsonKey.OTP, otp); - emailTemplateMap.put(JsonKey.OTP_EXPIRATION_IN_MINUTES, OTPUtil.getOTPExpirationInMinutes()); - Request emailRequest = null; - if (StringUtils.isBlank(otpType)) { - emailRequest = OTPUtil.sendOTPViaEmail(emailTemplateMap); - } else { - emailRequest = OTPUtil.sendOTPViaEmail(emailTemplateMap, RESET_PASSWORD); - } - tellToAnother(emailRequest); - } - - private void sendOTPViaSMS(String key, String otp) { - Map otpMap = new HashMap<>(); - otpMap.put(JsonKey.PHONE, key); - otpMap.put(JsonKey.OTP, otp); - otpMap.put(JsonKey.OTP_EXPIRATION_IN_MINUTES, OTPUtil.getOTPExpirationInMinutes()); - OTPUtil.sendOTPViaSMS(otpMap); - } -} +package org.sunbird.learner.actors.otp; + +import java.util.HashMap; +import java.util.Map; +import org.apache.commons.lang3.StringUtils; +import org.sunbird.actor.core.BaseActor; +import org.sunbird.actor.router.ActorConfig; +import org.sunbird.common.models.util.ActorOperations; +import org.sunbird.common.models.util.JsonKey; +import org.sunbird.common.models.util.LoggerEnum; +import org.sunbird.common.models.util.ProjectLogger; +import org.sunbird.common.request.Request; +import org.sunbird.learner.util.OTPUtil; + +@ActorConfig( + tasks = {}, + asyncTasks = {"sendOTP"} +) +public class SendOTPActor extends BaseActor { + public static final String RESET_PASSWORD = "resetPassword"; + + @Override + public void onReceive(Request request) throws Throwable { + + if (ActorOperations.SEND_OTP.getValue().equals(request.getOperation())) { + sendOTP(request); + } else { + onReceiveUnsupportedOperation("SendOTPActor"); + } + } + + private void sendOTP(Request request) { + String type = (String) request.getRequest().get(JsonKey.TYPE); + String key = (String) request.getRequest().get(JsonKey.KEY); + String otp = (String) request.getRequest().get(JsonKey.OTP); + if (JsonKey.EMAIL.equalsIgnoreCase(type) || JsonKey.PREV_USED_EMAIL.equalsIgnoreCase(type)|| JsonKey.RECOVERY_EMAIL.equalsIgnoreCase(type)) { + String userId = (String) request.get(JsonKey.USER_ID); + ProjectLogger.log( + "SendOTPActor:sendOTP userId found for send otp " + userId, LoggerEnum.INFO.name()); + sendOTPViaEmail(key, otp, userId); + } else if (JsonKey.PHONE.equalsIgnoreCase(type) + || JsonKey.PREV_USED_PHONE.equalsIgnoreCase(type) || JsonKey.RECOVERY_PHONE.equalsIgnoreCase(type)) { + sendOTPViaSMS(key, otp); + } + } + + private void sendOTPViaEmail(String key, String otp, String otpType) { + Map emailTemplateMap = new HashMap<>(); + emailTemplateMap.put(JsonKey.EMAIL, key); + emailTemplateMap.put(JsonKey.OTP, otp); + emailTemplateMap.put(JsonKey.OTP_EXPIRATION_IN_MINUTES, OTPUtil.getOTPExpirationInMinutes()); + Request emailRequest = null; + if (StringUtils.isBlank(otpType)) { + emailRequest = OTPUtil.sendOTPViaEmail(emailTemplateMap); + } else { + emailRequest = OTPUtil.sendOTPViaEmail(emailTemplateMap, RESET_PASSWORD); + } + tellToAnother(emailRequest); + } + + private void sendOTPViaSMS(String key, String otp) { + Map otpMap = new HashMap<>(); + otpMap.put(JsonKey.PHONE, key); + otpMap.put(JsonKey.OTP, otp); + otpMap.put(JsonKey.OTP_EXPIRATION_IN_MINUTES, OTPUtil.getOTPExpirationInMinutes()); + OTPUtil.sendOTPViaSMS(otpMap); + } +} diff --git a/actors/common/src/main/java/org/sunbird/learner/actors/otp/dao/OTPDao.java b/actors/common/src/main/java/org/sunbird/learner/actors/otp/dao/OTPDao.java index b0a83d703..c35bd4113 100644 --- a/actors/common/src/main/java/org/sunbird/learner/actors/otp/dao/OTPDao.java +++ b/actors/common/src/main/java/org/sunbird/learner/actors/otp/dao/OTPDao.java @@ -22,4 +22,11 @@ public interface OTPDao { */ void insertOTPDetails(String type, String key, String otp); + /** + * this method will be used to delete the Otp + * @param type + * @param key + */ + void deleteOtp(String type,String key); + } diff --git a/actors/common/src/main/java/org/sunbird/learner/actors/otp/dao/impl/OTPDaoImpl.java b/actors/common/src/main/java/org/sunbird/learner/actors/otp/dao/impl/OTPDaoImpl.java index e878da8f6..ea39c261a 100644 --- a/actors/common/src/main/java/org/sunbird/learner/actors/otp/dao/impl/OTPDaoImpl.java +++ b/actors/common/src/main/java/org/sunbird/learner/actors/otp/dao/impl/OTPDaoImpl.java @@ -9,6 +9,8 @@ import org.sunbird.cassandra.CassandraOperation; import org.sunbird.common.models.response.Response; import org.sunbird.common.models.util.JsonKey; +import org.sunbird.common.models.util.LoggerEnum; +import org.sunbird.common.models.util.ProjectLogger; import org.sunbird.common.models.util.PropertiesCache; import org.sunbird.helper.ServiceFactory; import org.sunbird.learner.actors.otp.dao.OTPDao; @@ -58,4 +60,13 @@ public void insertOTPDetails(String type, String key, String otp) { cassandraOperation.insertRecordWithTTL(Util.KEY_SPACE_NAME, TABLE_NAME, request, ttl); } + @Override + public void deleteOtp(String type, String key) { + MapcompositeKeyMap=new HashMap<>(); + compositeKeyMap.put(JsonKey.TYPE,type); + compositeKeyMap.put(JsonKey.KEY,key); + cassandraOperation.deleteRecord(JsonKey.SUNBIRD,TABLE_NAME,compositeKeyMap); + ProjectLogger.log("OTPDaoImpl:deleteOtp:otp deleted", LoggerEnum.INFO.name()); + } + } diff --git a/actors/common/src/main/java/org/sunbird/learner/actors/otp/service/OTPService.java b/actors/common/src/main/java/org/sunbird/learner/actors/otp/service/OTPService.java index dbcf98efb..1cbb64c25 100644 --- a/actors/common/src/main/java/org/sunbird/learner/actors/otp/service/OTPService.java +++ b/actors/common/src/main/java/org/sunbird/learner/actors/otp/service/OTPService.java @@ -22,6 +22,10 @@ public void insertOTPDetails(String type, String key, String otp) { otpDao.insertOTPDetails(type, key, otp); } + public void deleteOtp(String type,String key){ + otpDao.deleteOtp(type,key); + } + public static String getOTPSMSBody(Map smsTemplate) { try { Properties props = new Properties(); diff --git a/actors/common/src/main/java/org/sunbird/learner/actors/search/FuzzyMatcher.java b/actors/common/src/main/java/org/sunbird/learner/actors/search/FuzzyMatcher.java index b83b7c7e3..3601aad81 100644 --- a/actors/common/src/main/java/org/sunbird/learner/actors/search/FuzzyMatcher.java +++ b/actors/common/src/main/java/org/sunbird/learner/actors/search/FuzzyMatcher.java @@ -1,16 +1,16 @@ package org.sunbird.learner.actors.search; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - import com.intuit.fuzzymatcher.component.MatchService; import com.intuit.fuzzymatcher.domain.Document; import com.intuit.fuzzymatcher.domain.Element; import com.intuit.fuzzymatcher.domain.ElementType; import com.intuit.fuzzymatcher.domain.Match; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import org.sunbird.common.models.util.JsonKey; import org.sunbird.common.models.util.LoggerEnum; import org.sunbird.common.models.util.ProjectLogger; @@ -18,47 +18,99 @@ public class FuzzyMatcher { - private static final String nameToBeMatchedId="0"; + private static final String nameToBeMatchedId = "0"; + private static final String ENCODING = "UTF-8"; - public static List matchDoc(String nameToBeMatched,MapattributesValueMap) { - Document doc = new Document.Builder(nameToBeMatchedId).addElement(new Element.Builder().setType(ElementType.TEXT).setValue(nameToBeMatched).createElement()).setThreshold(getFuzzyThreshold()).createDocument(); - return match(doc, prepareDocumentFromSearchMap(attributesValueMap)); + public static List matchDoc( + String nameToBeMatched, Map attributesValueMap) { + Document doc = null; + try { + doc = + new Document.Builder(nameToBeMatchedId) + .addElement( + new Element.Builder() + .setType(ElementType.TEXT) + .setValue(URLEncoder.encode(nameToBeMatched, ENCODING)) + .createElement()) + .setThreshold(getFuzzyThreshold()) + .createDocument(); + } catch (UnsupportedEncodingException e) { + ProjectLogger.log( + "FuzzyMatcher:matchDoc: Error occured dueing encoding of data " + e, + LoggerEnum.ERROR.name()); } + return match(doc, prepareDocumentFromSearchMap(attributesValueMap)); + } - private static List match(Document doc, List docList) { - ListmatchedKeys=new ArrayList<>(); - MatchService matchService = new MatchService(); - Map>> map = matchService.applyMatch(doc, docList); - Iterator>>> itr = map.entrySet().iterator(); - List> matchList = null; - while (itr.hasNext()) { - Map.Entry>> entry = itr.next(); - matchList = entry.getValue(); - for (int i = 0; i < matchList.size(); i++) { - Match matchDoc = matchList.get(i); - matchedKeys.add( matchDoc.getMatchedWith().getKey()); - ProjectLogger.log(String.format("%s:%s:document matched doc: %s with id %s","FuzzyMatcher","match",matchDoc,matchDoc.getMatchedWith().getKey()), LoggerEnum.INFO.name()); - } - } - return matchedKeys; + private static List match(Document doc, List docList) { + List matchedKeys = new ArrayList<>(); + MatchService matchService = new MatchService(); + Map>> map = matchService.applyMatch(doc, docList); + Iterator>>> itr = map.entrySet().iterator(); + List> matchList = null; + while (itr.hasNext()) { + Map.Entry>> entry = itr.next(); + matchList = entry.getValue(); + for (int i = 0; i < matchList.size(); i++) { + Match matchDoc = matchList.get(i); + matchedKeys.add(matchDoc.getMatchedWith().getKey()); + ProjectLogger.log( + String.format( + "%s:%s:document matched doc: %s with id %s", + "FuzzyMatcher", "match", matchDoc, matchDoc.getMatchedWith().getKey()), + LoggerEnum.INFO.name()); + } } + return matchedKeys; + } - private static List prepareDocumentFromSearchMap(MapattributesValueMap) { - List docList = new ArrayList<>(); - attributesValueMap.entrySet().stream().forEach(result -> { - String[]attributes=result.getValue().split(" "); - ProjectLogger.log("FuzzyMatcher:prepareDocumentFromSearchMap: the name got for match ".concat(result.getValue()+"").concat("spliited name size is "+attributes.length),LoggerEnum.INFO.name()); - for(int i=0;i prepareDocumentFromSearchMap( + Map attributesValueMap) { + List docList = new ArrayList<>(); + attributesValueMap + .entrySet() + .stream() + .forEach( + result -> { + String[] attributes = result.getValue().split(" "); + ProjectLogger.log( + "FuzzyMatcher:prepareDocumentFromSearchMap: the name got for match " + .concat(result.getValue() + "") + .concat("spliited name size is " + attributes.length), + LoggerEnum.INFO.name()); + for (int i = 0; i < attributes.length; i++) { + try { + docList.add( + new Document.Builder(result.getKey()) + .addElement( + new Element.Builder() + .setType(ElementType.TEXT) + .setValue(URLEncoder.encode(attributes[i].trim(), ENCODING)) + .createElement()) + .createDocument()); + } catch (UnsupportedEncodingException e) { + ProjectLogger.log( + "Error occured during prepareDocumentFromSearchMap " + e, + LoggerEnum.ERROR.name()); + } + } + }); + ProjectLogger.log( + String.format( + "%s:%s:document size prepared to be matched is %s ", + "FuzzyMatcher", "prepareDocumentFromSearchMap", docList.size()), + LoggerEnum.INFO.name()); + return docList; + } - private static float getFuzzyThreshold(){ - String threshold= PropertiesCache.getInstance().readProperty(JsonKey.SUNBIRD_FUZZY_SEARCH_THRESHOLD); - ProjectLogger.log(String.format("%s:%s:the threshold got for Fuzzy search is %s","FuzzyMatcher","getFuzzyThreshold",threshold), LoggerEnum.INFO.name()); - return Float.parseFloat(threshold); - } -} + private static float getFuzzyThreshold() { + String threshold = + PropertiesCache.getInstance().readProperty(JsonKey.SUNBIRD_FUZZY_SEARCH_THRESHOLD); + ProjectLogger.log( + String.format( + "%s:%s:the threshold got for Fuzzy search is %s", + "FuzzyMatcher", "getFuzzyThreshold", threshold), + LoggerEnum.INFO.name()); + return Float.parseFloat(threshold); + } +} \ No newline at end of file diff --git a/actors/common/src/main/java/org/sunbird/learner/actors/search/FuzzySearchManager.java b/actors/common/src/main/java/org/sunbird/learner/actors/search/FuzzySearchManager.java index 34a2a1dd7..44927114b 100644 --- a/actors/common/src/main/java/org/sunbird/learner/actors/search/FuzzySearchManager.java +++ b/actors/common/src/main/java/org/sunbird/learner/actors/search/FuzzySearchManager.java @@ -1,76 +1,105 @@ package org.sunbird.learner.actors.search; +import java.util.*; import org.sunbird.common.exception.ProjectCommonException; import org.sunbird.common.models.util.JsonKey; import org.sunbird.common.models.util.LoggerEnum; import org.sunbird.common.models.util.ProjectLogger; import org.sunbird.common.responsecode.ResponseCode; -import java.util.*; - public class FuzzySearchManager { - private Map fuzzySearchMap; - private List> searchMap; - - private FuzzySearchManager(Map fuzzySearchMap, List> searchMap) { - this.fuzzySearchMap = fuzzySearchMap; - this.searchMap = searchMap; - } - - public static FuzzySearchManager getInstance(Map fuzzySearchMap, List> searchMap) { - return new FuzzySearchManager(fuzzySearchMap, searchMap); - } - - - protected List> startFuzzySearch() { - HashSet resultSet = new HashSet<>(); - fuzzySearchMap.entrySet().forEach(map -> { - validateKeyInFuzzyMap(map.getKey()); - String[] splittedName=map.getValue().toString().split(" "); - for(int i=0;i resultMap = searchMap.get(0); - if (!resultMap.keySet().contains(key)) { - ProjectLogger.log(String.format("%s:%s:key not found in searchMap %s",this.getClass().getSimpleName(),"getFuzzyAttributeFromMap",key), LoggerEnum.ERROR.name()); - ProjectCommonException.throwClientErrorException(ResponseCode.invalidRequestData); - } - } - - - private Map getFuzzyAttributeFromMap(String key) { - Map attributesValueMap = new HashMap<>(); - searchMap.stream().forEach(result -> { - Map resultMap = (Map) result; - attributesValueMap.put((String) resultMap.get(JsonKey.ID), (String) resultMap.get(key)); - - }); - ProjectLogger.log(String.format("%s:%s:the prepared Map for fuzzy search %s",this.getClass().getSimpleName(),"getFuzzyAttributeFromMap",Collections.singleton(attributesValueMap.toString())), LoggerEnum.INFO.name()); - return attributesValueMap; - } - - - private List> fetchResultSetFromSearchMap(HashSet fuzzyResultSet) { - - List> responseList = new ArrayList<>(); - - searchMap.stream().forEach(result -> { - Map resultMap = (Map) result; - if (fuzzyResultSet.contains(resultMap.get(JsonKey.ID))) { - responseList.add(resultMap); - } + private Map fuzzySearchMap; + private List> searchMap; + + private FuzzySearchManager( + Map fuzzySearchMap, List> searchMap) { + this.fuzzySearchMap = fuzzySearchMap; + this.searchMap = searchMap; + } + + public static FuzzySearchManager getInstance( + Map fuzzySearchMap, List> searchMap) { + return new FuzzySearchManager(fuzzySearchMap, searchMap); + } + + protected List> startFuzzySearch() { + HashSet resultSet = new HashSet<>(); + fuzzySearchMap + .entrySet() + .forEach( + map -> { + validateKeyInFuzzyMap(map.getKey()); + String[] splittedName = map.getValue().toString().split(" "); + for (int i = 0; i < splittedName.length; i++) { + resultSet.addAll( + FuzzyMatcher.matchDoc( + splittedName[i].trim(), getFuzzyAttributeFromMap(map.getKey()))); + } }); - ProjectLogger.log(String.format("%s:%s:returning only fuzzy matched result, the size of List of map is %s",this.getClass().getSimpleName(),"fetchResultSetFromSearchMap",responseList.size()), LoggerEnum.INFO.name()); - return responseList; - } - - private List> prepareResponseList(HashSet fuzzyResultSet){ - return fuzzyResultSet.size()!=0?fetchResultSetFromSearchMap(fuzzyResultSet): new ArrayList<>(); + ProjectLogger.log( + String.format( + "%s:%s:the size of resultSet i.e number of searches found is %s", + this.getClass().getSimpleName(), "startFuzzySearch", resultSet.size()), + LoggerEnum.INFO.name()); + return prepareResponseList(resultSet); + } + + private void validateKeyInFuzzyMap(String key) { + Map resultMap = searchMap.get(0); + if (!resultMap.keySet().contains(key)) { + ProjectLogger.log( + String.format( + "%s:%s:key not found in searchMap %s", + this.getClass().getSimpleName(), "getFuzzyAttributeFromMap", key), + LoggerEnum.ERROR.name()); + ProjectCommonException.throwClientErrorException(ResponseCode.invalidRequestData); } -} + } + + private Map getFuzzyAttributeFromMap(String key) { + Map attributesValueMap = new HashMap<>(); + searchMap + .stream() + .forEach( + result -> { + Map resultMap = (Map) result; + attributesValueMap.put( + (String) resultMap.get(JsonKey.ID), (String) resultMap.get(key)); + }); + ProjectLogger.log( + String.format( + "%s:%s:the prepared Map for fuzzy search %s", + this.getClass().getSimpleName(), + "getFuzzyAttributeFromMap", + Collections.singleton(attributesValueMap.toString())), + LoggerEnum.INFO.name()); + return attributesValueMap; + } + + private List> fetchResultSetFromSearchMap(HashSet fuzzyResultSet) { + + List> responseList = new ArrayList<>(); + + searchMap + .stream() + .forEach( + result -> { + Map resultMap = (Map) result; + if (fuzzyResultSet.contains(resultMap.get(JsonKey.ID))) { + responseList.add(resultMap); + } + }); + ProjectLogger.log( + String.format( + "%s:%s:returning only fuzzy matched result, the size of List of map is %s", + this.getClass().getSimpleName(), "fetchResultSetFromSearchMap", responseList.size()), + LoggerEnum.INFO.name()); + return responseList; + } + + private List> prepareResponseList(HashSet fuzzyResultSet) { + return fuzzyResultSet.size() != 0 + ? fetchResultSetFromSearchMap(fuzzyResultSet) + : new ArrayList<>(); + } +} \ No newline at end of file diff --git a/actors/common/src/main/java/org/sunbird/learner/actors/search/SearchHandlerActor.java b/actors/common/src/main/java/org/sunbird/learner/actors/search/SearchHandlerActor.java index af0196198..5cf497eda 100644 --- a/actors/common/src/main/java/org/sunbird/learner/actors/search/SearchHandlerActor.java +++ b/actors/common/src/main/java/org/sunbird/learner/actors/search/SearchHandlerActor.java @@ -21,6 +21,7 @@ import org.sunbird.common.factory.EsClientFactory; import org.sunbird.common.inf.ElasticSearchService; import org.sunbird.common.models.response.Response; +import org.sunbird.common.models.response.ResponseParams; import org.sunbird.common.models.util.ActorOperations; import org.sunbird.common.models.util.JsonKey; import org.sunbird.common.models.util.LoggerEnum; diff --git a/actors/common/src/main/java/org/sunbird/learner/util/UserFlagEnum.java b/actors/common/src/main/java/org/sunbird/learner/util/UserFlagEnum.java new file mode 100644 index 000000000..eabcec876 --- /dev/null +++ b/actors/common/src/main/java/org/sunbird/learner/util/UserFlagEnum.java @@ -0,0 +1,32 @@ +package org.sunbird.learner.util; + +import org.sunbird.common.models.util.JsonKey; + +/** + * UserFlagEnum provides all the flags of user type + * It contains flagtype and corresponding value in decimal format + * value should be bit enabled and equivalent to decimal format. + * If any flag is added, please add value as 2 pow (position-1) + */ +public enum UserFlagEnum { + PHONE_VERIFIED(JsonKey.PHONE_VERIFIED,1), + EMAIL_VERIFIED(JsonKey.EMAIL_VERIFIED,2), + STATE_VALIDATED(JsonKey.STATE_VALIDATED,4); + + private String userFlagType; + private int userFlagValue; + + UserFlagEnum(String userFlagType, int userFlagValue) { + this.userFlagType = userFlagType; + this.userFlagValue = userFlagValue; + } + + public int getUserFlagValue() { + return userFlagValue; + } + + public String getUserFlagType() { + return userFlagType; + } + +} diff --git a/actors/common/src/main/java/org/sunbird/learner/util/UserFlagUtil.java b/actors/common/src/main/java/org/sunbird/learner/util/UserFlagUtil.java new file mode 100644 index 000000000..69534f909 --- /dev/null +++ b/actors/common/src/main/java/org/sunbird/learner/util/UserFlagUtil.java @@ -0,0 +1,57 @@ +package org.sunbird.learner.util; + +import org.sunbird.common.models.util.JsonKey; + +import java.util.HashMap; +import java.util.Map; + +public class UserFlagUtil { + + /** + * This method return int value of the boolean flag + * @param userFlagType + * @param flagEnabled + * @return + */ + public static int getFlagValue(String userFlagType, boolean flagEnabled) { + int decimalValue = 0; + //if phone is verified flag should be true then only return flagvalue + if(userFlagType.equals(UserFlagEnum.PHONE_VERIFIED.getUserFlagType()) && + flagEnabled) { + decimalValue = UserFlagEnum.PHONE_VERIFIED.getUserFlagValue(); + } else if (userFlagType.equals(UserFlagEnum.EMAIL_VERIFIED.getUserFlagType()) && + flagEnabled) { + //if email is verified flag should be true then only return flagvalue + decimalValue = UserFlagEnum.EMAIL_VERIFIED.getUserFlagValue(); + } else if (userFlagType.equals(UserFlagEnum.STATE_VALIDATED.getUserFlagType()) && + flagEnabled) { + //if user is state-validated flag should be true then only return flagvalue + decimalValue = UserFlagEnum.STATE_VALIDATED.getUserFlagValue(); + } + return decimalValue; + } + + /** + * This method returns boolean flags of user for the flagValue + * @param flagsValue + * @return + */ + public static Map assignUserFlagValues(int flagsValue) { + Map userFlagMap = new HashMap<>(); + setDefaultValues(userFlagMap); + if((flagsValue & UserFlagEnum.PHONE_VERIFIED.getUserFlagValue())== UserFlagEnum.PHONE_VERIFIED.getUserFlagValue()) { + userFlagMap.put(UserFlagEnum.PHONE_VERIFIED.getUserFlagType(), true); + } if((flagsValue & UserFlagEnum.EMAIL_VERIFIED.getUserFlagValue())== UserFlagEnum.EMAIL_VERIFIED.getUserFlagValue()) { + userFlagMap.put(UserFlagEnum.EMAIL_VERIFIED.getUserFlagType(), true); + } if((flagsValue & UserFlagEnum.STATE_VALIDATED.getUserFlagValue())== UserFlagEnum.STATE_VALIDATED.getUserFlagValue()) { + userFlagMap.put(UserFlagEnum.STATE_VALIDATED.getUserFlagType(), true); + } + return userFlagMap; + } + + private static void setDefaultValues(Map userFlagMap) { + userFlagMap.put(JsonKey.EMAIL_VERIFIED, false); + userFlagMap.put(JsonKey.PHONE_VERIFIED, false); + userFlagMap.put(JsonKey.STATE_VALIDATED, false); + } +} diff --git a/actors/common/src/main/java/org/sunbird/learner/util/UserUtility.java b/actors/common/src/main/java/org/sunbird/learner/util/UserUtility.java index c6348c6ee..7d5381f08 100644 --- a/actors/common/src/main/java/org/sunbird/learner/util/UserUtility.java +++ b/actors/common/src/main/java/org/sunbird/learner/util/UserUtility.java @@ -6,6 +6,8 @@ import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.sunbird.common.models.util.JsonKey; +import org.sunbird.common.models.util.LoggerEnum; +import org.sunbird.common.models.util.ProjectLogger; import org.sunbird.common.models.util.PropertiesCache; import org.sunbird.common.models.util.datasecurity.DataMaskingService; import org.sunbird.common.models.util.datasecurity.DecryptionService; @@ -19,24 +21,17 @@ */ public final class UserUtility { - private static List userKeyToEncrypt = new ArrayList<>(); - private static List addressKeyToEncrypt = new ArrayList<>(); - private static List userKeyToDecrypt = new ArrayList<>(); - - private static DecryptionService decryptionService = - org.sunbird.common.models.util.datasecurity.impl.ServiceFactory.getDecryptionServiceInstance( - null); - private static DataMaskingService maskingService = - org.sunbird.common.models.util.datasecurity.impl.ServiceFactory.getMaskingServiceInstance( - null); + private static List userKeyToEncrypt; + private static List addressKeyToEncrypt; + private static List userKeyToDecrypt; + private static ListuserKeysToMasked; + private static DecryptionService decryptionService; + private static DataMaskingService maskingService; + private static ListphoneMaskedAttributes; + private static ListemailMaskedAttributes; static { - String userKey = PropertiesCache.getInstance().getProperty("userkey.encryption"); - userKeyToEncrypt = new ArrayList<>(Arrays.asList(userKey.split(","))); - String addressKey = PropertiesCache.getInstance().getProperty("addresskey.encryption"); - addressKeyToEncrypt = new ArrayList<>(Arrays.asList(addressKey.split(","))); - String userKeyDecrypt = PropertiesCache.getInstance().getProperty("userkey.decryption"); - userKeyToDecrypt = new ArrayList<>(Arrays.asList(userKeyDecrypt.split(","))); + init(); } private UserUtility() {} @@ -118,7 +113,7 @@ public static Map decryptUserDataFrmES(Map userM // Decrypt user basic info for (String key : userKeyToDecrypt) { if (userMap.containsKey(key)) { - if (JsonKey.EMAIL.equalsIgnoreCase(key) || JsonKey.PHONE.equalsIgnoreCase(key) || JsonKey.PREV_USED_EMAIL.equalsIgnoreCase(key)|| JsonKey.PREV_USED_PHONE.equalsIgnoreCase(key)) { + if (userKeysToMasked.contains(key)) { userMap.put(key, maskEmailOrPhone((String) userMap.get(key), key)); } else { userMap.put(key, service.decryptData((String) userMap.get(key))); @@ -184,11 +179,37 @@ public static String maskEmailOrPhone(String encryptedEmailOrPhone, String type) if (StringUtils.isEmpty(encryptedEmailOrPhone)) { return StringUtils.EMPTY; } - if (JsonKey.PHONE.equals(type) || JsonKey.PREV_USED_PHONE.equals(type)) { + if (phoneMaskedAttributes.contains(type)) { return maskingService.maskPhone(decryptionService.decryptData(encryptedEmailOrPhone)); - } else if (JsonKey.EMAIL.equals(type) || JsonKey.PREV_USED_EMAIL.equals(type)) { + } else if (emailMaskedAttributes.contains(type)) { return maskingService.maskEmail(decryptionService.decryptData(encryptedEmailOrPhone)); } return StringUtils.EMPTY; } + + private static void init(){ + decryptionService = + org.sunbird.common.models.util.datasecurity.impl.ServiceFactory.getDecryptionServiceInstance( + null); + maskingService = + org.sunbird.common.models.util.datasecurity.impl.ServiceFactory.getMaskingServiceInstance( + null); + String userKey = PropertiesCache.getInstance().getProperty("userkey.encryption"); + userKeyToEncrypt = new ArrayList<>(Arrays.asList(userKey.split(","))); + ProjectLogger.log("UserUtility:init:user encrypt attributes got".concat(userKey+""), LoggerEnum.INFO.name()); + String addressKey = PropertiesCache.getInstance().getProperty("addresskey.encryption"); + ProjectLogger.log("UserUtility:init:user address encrypt attributes got".concat(addressKey+""), LoggerEnum.INFO.name()); + addressKeyToEncrypt = new ArrayList<>(Arrays.asList(addressKey.split(","))); + String userKeyDecrypt = PropertiesCache.getInstance().getProperty("userkey.decryption"); + String userKeyToMasked=PropertiesCache.getInstance().getProperty("userkey.masked"); + userKeyToDecrypt = new ArrayList<>(Arrays.asList(userKeyDecrypt.split(","))); + userKeysToMasked=new ArrayList<>(Arrays.asList(userKeyToMasked.split(","))); + String emailTypeAttributeKey=PropertiesCache.getInstance().getProperty("userkey.emailtypeattributes"); + String phoneTypeAttributeKey=PropertiesCache.getInstance().getProperty("userkey.phonetypeattributes"); + emailMaskedAttributes=new ArrayList<>(Arrays.asList(emailTypeAttributeKey.split(","))); + ProjectLogger.log("UserUtility:init:email masked attributes got".concat(emailTypeAttributeKey+""), LoggerEnum.INFO.name()); + phoneMaskedAttributes=new ArrayList<>(Arrays.asList(phoneTypeAttributeKey.split(","))); + ProjectLogger.log("UserUtility:init:phone masked attributes got".concat(phoneTypeAttributeKey+""),LoggerEnum.INFO.name()); + + } } diff --git a/actors/common/src/main/java/org/sunbird/learner/util/Util.java b/actors/common/src/main/java/org/sunbird/learner/util/Util.java index 179b691c0..17b45c955 100644 --- a/actors/common/src/main/java/org/sunbird/learner/util/Util.java +++ b/actors/common/src/main/java/org/sunbird/learner/util/Util.java @@ -1469,7 +1469,9 @@ public static Map getUserDetails(String userId, ActorRef actorRe // save masked email and phone number addMaskEmailAndPhone(userDetails); checkProfileCompleteness(userDetails); - checkUserProfileVisibility(userDetails, actorRef); + if(actorRef!=null) { + checkUserProfileVisibility(userDetails, actorRef); + } userDetails.remove(JsonKey.PASSWORD); addEmailAndPhone(userDetails); checkEmailAndPhoneVerified(userDetails); @@ -1488,12 +1490,9 @@ public static void addEmailAndPhone(Map userDetails) { } public static void checkEmailAndPhoneVerified(Map userDetails) { - if (StringUtils.isBlank((String) userDetails.get(JsonKey.EMAIL))) { - userDetails.put(JsonKey.EMAIL_VERIFIED, false); - } - if (StringUtils.isBlank((String) userDetails.get(JsonKey.PHONE))) { - userDetails.put(JsonKey.PHONE_VERIFIED, false); - } + int flagsValue = Integer.parseInt(userDetails.get(JsonKey.FLAGS_VALUE).toString()); + Map userFlagMap = UserFlagUtil.assignUserFlagValues(flagsValue); + userDetails.putAll(userFlagMap); } public static void checkProfileCompleteness(Map userMap) { diff --git a/actors/common/src/main/java/org/sunbird/models/user/User.java b/actors/common/src/main/java/org/sunbird/models/user/User.java index 84a8f404f..cd039bac6 100644 --- a/actors/common/src/main/java/org/sunbird/models/user/User.java +++ b/actors/common/src/main/java/org/sunbird/models/user/User.java @@ -26,7 +26,6 @@ public class User implements Serializable { private String createdDate; private String dob; private String email; - private Boolean emailVerified; private String firstName; private String gender; private List grade; @@ -36,7 +35,6 @@ public class User implements Serializable { private String lastName; private String location; private String phone; - private Boolean phoneVerified; private String profileSummary; private Map profileVisibility; private String provider; @@ -68,6 +66,9 @@ public class User implements Serializable { private List locationIds; private String prevUsedPhone; private String prevUsedEmail; + private int flagsValue; + private String recoveryEmail; + private String recoveryPhone; public List getLocationIds() { return locationIds; @@ -173,14 +174,6 @@ public void setEmail(String email) { this.email = email; } - public Boolean getEmailVerified() { - return emailVerified; - } - - public void setEmailVerified(Boolean emailVerified) { - this.emailVerified = emailVerified; - } - public String getFirstName() { return firstName; } @@ -406,14 +399,6 @@ public void setFramework(Map> framework) { this.framework = framework; } - public Boolean getPhoneVerified() { - return phoneVerified; - } - - public void setPhoneVerified(Boolean phoneVerified) { - this.phoneVerified = phoneVerified; - } - public Timestamp getTncAcceptedOn() { return tncAcceptedOn; } @@ -462,4 +447,28 @@ public String getPrevUsedEmail() { public void setPrevUsedEmail(String prevUsedEmail) { this.prevUsedEmail = prevUsedEmail; } -} + + public int getFlagsValue() { + return flagsValue; + } + + public void setFlagsValue(int flagsValue) { + this.flagsValue = flagsValue; + } + public String getRecoveryEmail() { + return recoveryEmail; + } + + public void setRecoveryEmail(String recoveryEmail) { + this.recoveryEmail = recoveryEmail; + } + + public String getRecoveryPhone() { + return recoveryPhone; + } + + public void setRecoveryPhone(String recoveryPhone) { + this.recoveryPhone = recoveryPhone; + } + +} \ No newline at end of file diff --git a/actors/common/src/main/java/org/sunbird/validator/user/UserBulkMigrationRequestValidator.java b/actors/common/src/main/java/org/sunbird/validator/user/UserBulkMigrationRequestValidator.java new file mode 100644 index 000000000..5d0e0d967 --- /dev/null +++ b/actors/common/src/main/java/org/sunbird/validator/user/UserBulkMigrationRequestValidator.java @@ -0,0 +1,184 @@ +package org.sunbird.validator.user; + +import org.apache.commons.lang3.StringUtils; +import org.sunbird.bean.MigrationUser; +import org.sunbird.bean.ShadowUserUpload; +import org.sunbird.common.exception.ProjectCommonException; +import org.sunbird.common.models.util.JsonKey; +import org.sunbird.common.models.util.LoggerEnum; +import org.sunbird.common.models.util.ProjectLogger; +import org.sunbird.common.models.util.ProjectUtil; +import org.sunbird.common.responsecode.ResponseCode; +import org.sunbird.error.CsvError; +import org.sunbird.error.CsvRowErrorDetails; +import org.sunbird.error.ErrorEnum; +import org.sunbird.error.IErrorDispatcher; +import org.sunbird.error.factory.ErrorDispatcherFactory; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.regex.Pattern; + +/** + * this class will validate the csv file for shadow db + * @author anmolgupta + */ +public class UserBulkMigrationRequestValidator { + + private ShadowUserUpload shadowUserMigration; + private HashSet userExternalIdsSet=new HashSet<>(); + private CsvError csvRowsErrors=new CsvError(); + private static final int MAX_ROW_SUPPORTED=20000; + private static final String NAME_REG_MATCHER="^[^.][^^;\\-()<>|!=’%_#$]+"; + + private UserBulkMigrationRequestValidator(ShadowUserUpload migration) { + this.shadowUserMigration = migration; + } + public static UserBulkMigrationRequestValidator getInstance(ShadowUserUpload migration){ + return new UserBulkMigrationRequestValidator(migration); + } + public void validate() + { ProjectLogger.log("UserBulkMigrationRequestValidator:validate:start validating migration users", LoggerEnum.INFO.name()); + checkCsvRows(); + } + private void checkCsvRows(){ + validateRowsCount(); + shadowUserMigration.getValues().stream().forEach(onCsvRow -> { + int index=shadowUserMigration.getValues().indexOf(onCsvRow); + validateMigrationUser(onCsvRow,index); + }); + if(csvRowsErrors.getErrorsList().size()>0){ + IErrorDispatcher errorDispatcher= ErrorDispatcherFactory.getErrorDispatcher(csvRowsErrors); + errorDispatcher.dispatchError(); + } + } + + private void validateRowsCount(){ + int ROW_BEGINNING_INDEX=1; + if(shadowUserMigration.getValues().size()>=MAX_ROW_SUPPORTED){ + throw new ProjectCommonException( + ResponseCode.csvRowsExceeds.getErrorCode(), + ResponseCode.csvRowsExceeds.getErrorMessage().concat("supported:"+MAX_ROW_SUPPORTED), + ResponseCode.CLIENT_ERROR.getResponseCode()); + } + else if(shadowUserMigration.getValues().size() identifiers = (List) request.get(JsonKey.IDENTIFIER); - freeUpUserIdentifier(id, identifiers); + private CassandraOperation cassandraOperation = ServiceFactory.getInstance(); + private Util.DbInfo usrDbInfo = Util.dbInfoMap.get(JsonKey.USER_DB); + public static final int FIRST_RECORD = 0; + private static ElasticSearchService esUtil = new ElasticSearchTcpImpl(); + + @Override + public void onReceive(Request request) { + String id = (String) request.get(JsonKey.ID); + List identifiers = (List) request.get(JsonKey.IDENTIFIER); + freeUpUserIdentifier(id, identifiers); + } + + private Map getUserById(String id) { + Response response = + cassandraOperation.getRecordById(usrDbInfo.getKeySpace(), usrDbInfo.getTableName(), id); + List> responseList = (List) response.get(JsonKey.RESPONSE); + if (CollectionUtils.isEmpty(responseList)) { + ProjectLogger.log( + String.format( + "%s:%s:User not found with provided Id:%s", + this.getClass().getSimpleName(), "getById", id), + LoggerEnum.ERROR.name()); + ProjectCommonException.throwClientErrorException(ResponseCode.invalidUserId); } - - private Map getUserById(String id) { - Response response = cassandraOperation.getRecordById(usrDbInfo.getKeySpace(), usrDbInfo.getTableName(), id); - List> responseList = (List) response.get(JsonKey.RESPONSE); - if (CollectionUtils.isEmpty(responseList)) { - ProjectLogger.log(String.format("%s:%s:User not found with provided Id:%s", this.getClass().getSimpleName(), "getById", id), LoggerEnum.ERROR.name()); - ProjectCommonException.throwClientErrorException(ResponseCode.invalidUserId); - } - return responseList.get(FIRST_RECORD); + return responseList.get(FIRST_RECORD); + } + + private Response processUserAttribute(Map userDbMap, List identifiers) { + + if (identifiers.contains(JsonKey.EMAIL)) { + nullifyEmail(userDbMap); + ProjectLogger.log( + String.format( + "%s:%s:Nullified Email. WITH ID %s", + this.getClass().getSimpleName(), "freeUpUserIdentifier", userDbMap.get(JsonKey.ID)), + LoggerEnum.INFO.name()); } - - private Response processUserAttribute(Map userDbMap, List identifiers) { - - if (identifiers.contains(JsonKey.EMAIL)) { - nullifyEmail(userDbMap); - ProjectLogger.log(String.format("%s:%s:Nullified Email. WITH ID %s", this.getClass().getSimpleName(), "freeUpUserIdentifier", userDbMap.get(JsonKey.ID)), LoggerEnum.INFO.name()); - } - if (identifiers.contains(JsonKey.PHONE)) { - nullifyPhone(userDbMap); - ProjectLogger.log(String.format("%s:%s:Nullified Phone. WITH ID %s", this.getClass().getSimpleName(), "freeUpUserIdentifier", userDbMap.get(JsonKey.ID)), LoggerEnum.INFO.name()); - } - return updateUser(userDbMap); + if (identifiers.contains(JsonKey.PHONE)) { + nullifyPhone(userDbMap); + ProjectLogger.log( + String.format( + "%s:%s:Nullified Phone. WITH ID %s", + this.getClass().getSimpleName(), "freeUpUserIdentifier", userDbMap.get(JsonKey.ID)), + LoggerEnum.INFO.name()); } - - private void nullifyEmail(Map userDbMap) { - if (isNullifyOperationValid((String) userDbMap.get(JsonKey.EMAIL))) { - userDbMap.replace(JsonKey.PREV_USED_EMAIL, userDbMap.get(JsonKey.EMAIL)); - userDbMap.replace(JsonKey.EMAIL, null); - userDbMap.replace(JsonKey.MASKED_EMAIL, null); - userDbMap.replace(JsonKey.EMAIL_VERIFIED, false); - } + return updateUser(userDbMap); + } + + private void nullifyEmail(Map userDbMap) { + if (isNullifyOperationValid((String) userDbMap.get(JsonKey.EMAIL))) { + userDbMap.replace(JsonKey.PREV_USED_EMAIL, userDbMap.get(JsonKey.EMAIL)); + userDbMap.replace(JsonKey.EMAIL, null); + userDbMap.replace(JsonKey.MASKED_EMAIL, null); + userDbMap.replace(JsonKey.EMAIL_VERIFIED, false); + userDbMap.put( + JsonKey.FLAGS_VALUE, calculateFlagvalue(UserFlagEnum.EMAIL_VERIFIED, userDbMap)); } - - private void nullifyPhone(Map userDbMap) { - if (isNullifyOperationValid((String) userDbMap.get(JsonKey.PHONE))) { - userDbMap.replace(JsonKey.PREV_USED_PHONE, userDbMap.get(JsonKey.PHONE)); - userDbMap.replace(JsonKey.PHONE, null); - userDbMap.replace(JsonKey.MASKED_PHONE, null); - userDbMap.replace(JsonKey.PHONE_VERIFIED, false); - userDbMap.replace(JsonKey.COUNTRY_CODE, null); - } + } + + private void nullifyPhone(Map userDbMap) { + if (isNullifyOperationValid((String) userDbMap.get(JsonKey.PHONE))) { + userDbMap.replace(JsonKey.PREV_USED_PHONE, userDbMap.get(JsonKey.PHONE)); + userDbMap.replace(JsonKey.PHONE, null); + userDbMap.replace(JsonKey.MASKED_PHONE, null); + userDbMap.replace(JsonKey.PHONE_VERIFIED, false); + userDbMap.replace(JsonKey.COUNTRY_CODE, null); + userDbMap.put( + JsonKey.FLAGS_VALUE, calculateFlagvalue(UserFlagEnum.PHONE_VERIFIED, userDbMap)); } - - private Response updateUser(Map userDbMap) { - return cassandraOperation.updateRecord(usrDbInfo.getKeySpace(), usrDbInfo.getTableName(), userDbMap); - } - - private void freeUpUserIdentifier(String id, List identifiers) { - Map userDbMap = getUserById(id); - Response response = processUserAttribute(userDbMap, identifiers); - sender().tell(response, self()); - ProjectLogger.log(String.format("%s:%s:USER MAP SUCCESSFULLY UPDATED IN CASSANDRA. WITH ID %s", this.getClass().getSimpleName(), "freeUpUserIdentifier", userDbMap.get(JsonKey.ID)), LoggerEnum.INFO.name()); - saveUserDetailsToEs(userDbMap); - } - - private void saveUserDetailsToEs(Map userDbMap) { - Request userUpdatedRequest = new Request(); - userUpdatedRequest.setOperation(ActorOperations.UPDATE_USER_INFO_ELASTIC.getValue()); - userUpdatedRequest.getRequest().put(JsonKey.ID, userDbMap.get(JsonKey.ID)); - ProjectLogger.log(String.format("%s:%s:Trigger for sync user details to ES with user updated user Id:%s", this.getClass().getSimpleName(), "saveUserDetailsToEs", userDbMap.get(JsonKey.ID)), LoggerEnum.INFO.name()); - tellToAnother(userUpdatedRequest); - } - - private boolean isNullifyOperationValid(String identifier) { - return StringUtils.isNotBlank(identifier); + } + + private int calculateFlagvalue(UserFlagEnum identifier, Map userDbMap) { + int flagsValue = 0; + if (userDbMap.get(JsonKey.FLAGS_VALUE) != null + && (int) userDbMap.get(JsonKey.FLAGS_VALUE) > 0) { + flagsValue = (int) userDbMap.get(JsonKey.FLAGS_VALUE); + flagsValue = flagsValue - identifier.getUserFlagValue(); } + return flagsValue; + } + + private Response updateUser(Map userDbMap) { + return cassandraOperation.updateRecord( + usrDbInfo.getKeySpace(), usrDbInfo.getTableName(), userDbMap); + } + + private void freeUpUserIdentifier(String id, List identifiers) { + Map userDbMap = getUserById(id); + Response response = processUserAttribute(userDbMap, identifiers); + boolean esResponse = saveUserDetailsToEs(userDbMap); + ProjectLogger.log( + "IdentifierFreeUpActor:freeUpUserIdentifier response got from ES for identifier freeup api :" + + esResponse, + LoggerEnum.INFO.name()); + sender().tell(response, self()); + ProjectLogger.log( + String.format( + "%s:%s:USER MAP SUCCESSFULLY UPDATED IN CASSANDRA. WITH ID %s", + this.getClass().getSimpleName(), "freeUpUserIdentifier", userDbMap.get(JsonKey.ID)), + LoggerEnum.INFO.name()); + } + + private boolean saveUserDetailsToEs(Map userDbMap) { + Future future = + esUtil.update( + ProjectUtil.EsType.user.getTypeName(), (String) userDbMap.get(JsonKey.ID), userDbMap); + return (boolean) ElasticSearchHelper.getResponseFromFuture(future); + } + + private boolean isNullifyOperationValid(String identifier) { + return StringUtils.isNotBlank(identifier); + } } \ No newline at end of file diff --git a/actors/user/src/main/java/org/sunbird/user/actors/TenantMigrationActor.java b/actors/user/src/main/java/org/sunbird/user/actors/TenantMigrationActor.java index cc0c10c30..b4bdc25a3 100644 --- a/actors/user/src/main/java/org/sunbird/user/actors/TenantMigrationActor.java +++ b/actors/user/src/main/java/org/sunbird/user/actors/TenantMigrationActor.java @@ -3,13 +3,12 @@ import akka.actor.ActorRef; import com.fasterxml.jackson.databind.ObjectMapper; import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; + import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.StringUtils; +import org.sunbird.actor.background.BackgroundOperations; import org.sunbird.actor.core.BaseActor; import org.sunbird.actor.router.ActorConfig; import org.sunbird.actorutil.InterServiceCommunication; @@ -27,11 +26,14 @@ import org.sunbird.common.models.util.ProjectUtil; import org.sunbird.common.models.util.StringFormatter; import org.sunbird.common.models.util.TelemetryEnvKey; +import org.sunbird.common.models.util.datasecurity.DataMaskingService; +import org.sunbird.common.models.util.datasecurity.DecryptionService; import org.sunbird.common.request.ExecutionContext; import org.sunbird.common.request.Request; import org.sunbird.common.responsecode.ResponseCode; import org.sunbird.helper.ServiceFactory; import org.sunbird.learner.organisation.external.identity.service.OrgExternalService; +import org.sunbird.learner.util.UserFlagEnum; import org.sunbird.learner.util.Util; import org.sunbird.models.user.User; import org.sunbird.telemetry.util.TelemetryUtil; @@ -63,6 +65,13 @@ public class TenantMigrationActor extends BaseActor { private ObjectMapper mapper = new ObjectMapper(); private ActorRef systemSettingActorRef = null; private ElasticSearchService esUtil = EsClientFactory.getInstance(JsonKey.REST); + private static final String ACCOUNT_MERGE_EMAIL_TEMPLATE = "accountMerge"; + private static final String MASK_IDENTIFIER = "maskIdentifier"; + DecryptionService decryptionService = + org.sunbird.common.models.util.datasecurity.impl.ServiceFactory.getDecryptionServiceInstance( + ""); + DataMaskingService maskingService = + org.sunbird.common.models.util.datasecurity.impl.ServiceFactory.getMaskingServiceInstance(""); @Override public void onReceive(Request request) throws Throwable { @@ -99,6 +108,11 @@ private void migrateUser(Request request) { context.getRequestContext().put(JsonKey.ROLLUP, rollup); String orgId = validateOrgExternalIdOrOrgIdAndGetOrgId(request.getRequest()); request.getRequest().put(JsonKey.ORG_ID, orgId); + int userFlagValue = UserFlagEnum.STATE_VALIDATED.getUserFlagValue(); + if(userDetails.containsKey(JsonKey.FLAGS_VALUE)) { + userFlagValue += Integer.parseInt(String.valueOf(userDetails.get(JsonKey.FLAGS_VALUE))); + } + request.getRequest().put(JsonKey.FLAGS_VALUE, userFlagValue); Map userUpdateRequest = createUserUpdateRequest(request); // Update user channel and rootOrgId Response response = @@ -139,11 +153,53 @@ private void migrateUser(Request request) { sender().tell(response, self()); // save user data to ES saveUserDetailsToEs((String) request.getRequest().get(JsonKey.USER_ID)); + notify(userDetails); targetObject = TelemetryUtil.generateTargetObject( (String) reqMap.get(JsonKey.USER_ID), TelemetryEnvKey.USER, MIGRATE, null); TelemetryUtil.telemetryProcessingCall(reqMap, targetObject, correlatedObject); } + private void notify(Map userDetail) { + ProjectLogger.log("notify starts sending migrate notification to user " + userDetail.get(JsonKey.USER_ID)); + Map userData = createUserData(userDetail); + Request notificationRequest = createNotificationData(userData); + tellToAnother(notificationRequest); + } + + private Request createNotificationData(Map userData) { + Request request = new Request(); + Map requestMap = new HashMap<>(); + requestMap.put(JsonKey.NAME, userData.get(JsonKey.FIRST_NAME)); + requestMap.put(JsonKey.FIRST_NAME, userData.get(JsonKey.FIRST_NAME)); + if (StringUtils.isNotBlank((String) userData.get(JsonKey.EMAIL))) { + requestMap.put(JsonKey.RECIPIENT_EMAILS, Arrays.asList(userData.get(JsonKey.EMAIL))); + } else { + requestMap.put(JsonKey.RECIPIENT_PHONES, Arrays.asList(userData.get(JsonKey.PHONE))); + requestMap.put(JsonKey.MODE, JsonKey.SMS); + } + requestMap.put(JsonKey.EMAIL_TEMPLATE_TYPE, ACCOUNT_MERGE_EMAIL_TEMPLATE); + String body = + MessageFormat.format( + ProjectUtil.getConfigValue(JsonKey.SUNBIRD_MIGRATE_USER_BODY), + ProjectUtil.getConfigValue(JsonKey.SUNBIRD_INSTALLATION), + userData.get(MASK_IDENTIFIER)); + requestMap.put(JsonKey.BODY, body); + requestMap.put(JsonKey.SUBJECT, ProjectUtil.getConfigValue(JsonKey.SUNBIRD_ACCOUNT_MERGE_SUBJECT)); + request.getRequest().put(JsonKey.EMAIL_REQUEST, requestMap); + request.setOperation(BackgroundOperations.emailService.name()); + return request; + } + + private Map createUserData(Map userData) { + if (StringUtils.isNotBlank((String) userData.get(JsonKey.EMAIL))) { + userData.put(JsonKey.EMAIL,decryptionService.decryptData((String) userData.get(JsonKey.EMAIL))); + userData.put(MASK_IDENTIFIER,maskingService.maskEmail((String)userData.get(JsonKey.EMAIL))); + } else { + userData.put(JsonKey.PHONE, decryptionService.decryptData((String) userData.get(JsonKey.PHONE))); + userData.put(MASK_IDENTIFIER, maskingService.maskPhone((String) userData.get(JsonKey.PHONE))); + } + return userData; + } private String validateOrgExternalIdOrOrgIdAndGetOrgId(Map migrateReq) { ProjectLogger.log( @@ -319,6 +375,8 @@ private Map createUserUpdateRequest(Request request) { userRequest.put(JsonKey.ID, request.getRequest().get(JsonKey.USER_ID)); userRequest.put(JsonKey.CHANNEL, request.getRequest().get(JsonKey.CHANNEL)); userRequest.put(JsonKey.ROOT_ORG_ID, request.getRequest().get(JsonKey.ROOT_ORG_ID)); + userRequest.put(JsonKey.FLAGS_VALUE, request.getRequest().get(JsonKey.FLAGS_VALUE)); + userRequest.put(JsonKey.USER_TYPE, JsonKey.TEACHER); return userRequest; } } diff --git a/actors/user/src/main/java/org/sunbird/user/actors/UserManagementActor.java b/actors/user/src/main/java/org/sunbird/user/actors/UserManagementActor.java index 3b315fddb..67a60196d 100644 --- a/actors/user/src/main/java/org/sunbird/user/actors/UserManagementActor.java +++ b/actors/user/src/main/java/org/sunbird/user/actors/UserManagementActor.java @@ -39,11 +39,13 @@ import org.sunbird.common.request.UserRequestValidator; import org.sunbird.common.responsecode.ResponseCode; import org.sunbird.common.responsecode.ResponseMessage; +import org.sunbird.common.util.Matcher; import org.sunbird.content.store.util.ContentStoreUtil; import org.sunbird.helper.ServiceFactory; import org.sunbird.learner.actors.role.service.RoleService; import org.sunbird.learner.organisation.external.identity.service.OrgExternalService; import org.sunbird.learner.util.DataCacheHandler; +import org.sunbird.learner.util.UserFlagUtil; import org.sunbird.learner.util.Util; import org.sunbird.models.organisation.Organisation; import org.sunbird.models.user.User; @@ -148,12 +150,25 @@ private void updateUser(Request actorMessage) { userMap.put(JsonKey.UPDATED_BY, actorMessage.getContext().get(JsonKey.REQUESTED_BY)); } Map requestMap = UserUtil.encryptUserData(userMap); + validateRecoveryEmailPhone(userDbRecord, userMap); UserUtil.addMaskEmailAndMaskPhone(requestMap); removeUnwanted(requestMap); if (requestMap.containsKey(JsonKey.TNC_ACCEPTED_ON)) { requestMap.put( JsonKey.TNC_ACCEPTED_ON, new Timestamp((Long) requestMap.get(JsonKey.TNC_ACCEPTED_ON))); } + if (requestMap.containsKey(JsonKey.RECOVERY_EMAIL) + && StringUtils.isBlank((String) requestMap.get(JsonKey.RECOVERY_EMAIL))) { + requestMap.put(JsonKey.RECOVERY_EMAIL, null); + } + if (requestMap.containsKey(JsonKey.RECOVERY_PHONE) + && StringUtils.isBlank((String) requestMap.get(JsonKey.RECOVERY_PHONE))) { + requestMap.put(JsonKey.RECOVERY_PHONE, null); + } + + Map userBooleanMap = updatedUserFlagsMap(userMap, userDbRecord); + int userFlagValue = userFlagsToNum(userBooleanMap); + requestMap.put(JsonKey.FLAGS_VALUE, userFlagValue); Response response = cassandraOperation.updateRecord( usrDbInfo.getKeySpace(), usrDbInfo.getTableName(), requestMap); @@ -314,9 +329,7 @@ private void validateUserTypeForUpdate(Map userMap) { String custodianRootOrgId = null; User user = userService.getUserById((String) userMap.get(JsonKey.USER_ID)); try { - custodianChannel = - userService.getCustodianChannel(new HashMap<>(), systemSettingActorRef); - custodianRootOrgId = userService.getRootOrgIdFromChannel(custodianChannel); + custodianRootOrgId = getCustodianRootOrgId(); } catch (Exception ex) { ProjectLogger.log( "UserManagementActor: validateUserTypeForUpdate :" @@ -374,7 +387,6 @@ private void validateUserFrameworkData( private void removeFieldsFrmReq(Map userMap) { userMap.remove(JsonKey.ENC_EMAIL); userMap.remove(JsonKey.ENC_PHONE); - userMap.remove(JsonKey.EMAIL_VERIFIED); userMap.remove(JsonKey.STATUS); userMap.remove(JsonKey.PROVIDER); userMap.remove(JsonKey.USERNAME); @@ -412,6 +424,7 @@ private void createUser(Request actorMessage) { userRequestValidator.validateCreateUserV1Request(actorMessage); } validateChannelAndOrganisationId(userMap); + validatePrimaryAndRecoveryKeys(userMap); // remove these fields from req userMap.remove(JsonKey.ENC_EMAIL); @@ -483,12 +496,9 @@ private void validateUserType(Map userMap, boolean isCustodianOr ResponseCode.errorTeacherCannotBelongToCustodianOrg, ResponseCode.errorTeacherCannotBelongToCustodianOrg.getErrorMessage()); } else if (UserType.TEACHER.getTypeName().equalsIgnoreCase(userType)) { - String custodianChannel = null; String custodianRootOrgId = null; try { - custodianChannel = - userService.getCustodianChannel(new HashMap<>(), systemSettingActorRef); - custodianRootOrgId = userService.getRootOrgIdFromChannel(custodianChannel); + custodianRootOrgId = getCustodianRootOrgId(); } catch (Exception ex) { ProjectLogger.log( "UserManagementActor: validateUserType :" @@ -573,7 +583,13 @@ private void processUserRequest( UserUtil.addMaskEmailAndMaskPhone(requestMap); removeUnwanted(requestMap); requestMap.put(JsonKey.IS_DELETED, false); - + Map userFlagsMap = new HashMap<>(); + // checks if the user is belongs to state and sets a validation flag + setStateValidation(requestMap, userFlagsMap); + userFlagsMap.put(JsonKey.EMAIL_VERIFIED, (Boolean) userMap.get(JsonKey.EMAIL_VERIFIED)); + userFlagsMap.put(JsonKey.PHONE_VERIFIED, (Boolean) userMap.get(JsonKey.PHONE_VERIFIED)); + int userFlagValue = userFlagsToNum(userFlagsMap); + requestMap.put(JsonKey.FLAGS_VALUE, userFlagValue); Response response = null; boolean isPasswordUpdated = false; try { @@ -641,6 +657,77 @@ private void processUserRequest( TelemetryUtil.telemetryProcessingCall(userMap, targetObject, correlatedObject); } + private int userFlagsToNum(Map userBooleanMap) { + int userFlagValue = 0; + Set> mapEntry = userBooleanMap.entrySet(); + for (Map.Entry entry : mapEntry) { + if (StringUtils.isNotEmpty(entry.getKey())) { + userFlagValue += UserFlagUtil.getFlagValue(entry.getKey(), entry.getValue()); + } + } + return userFlagValue; + } + + private void setStateValidation( + Map requestMap, Map userBooleanMap) { + String rootOrgId = (String) requestMap.get(JsonKey.ROOT_ORG_ID); + String custodianRootOrgId = getCustodianRootOrgId(); + // if the user is creating for non-custodian(i.e state) the value is set as true else false + userBooleanMap.put(JsonKey.STATE_VALIDATED, !custodianRootOrgId.equals(rootOrgId)); + } + + private Map updatedUserFlagsMap( + Map userMap, Map userDbRecord) { + Map userBooleanMap = new HashMap<>(); + setUserFlagValue(userDbRecord, JsonKey.EMAIL, JsonKey.EMAIL_VERIFIED); + setUserFlagValue(userDbRecord, JsonKey.PHONE, JsonKey.PHONE_VERIFIED); + boolean emailVerified = + (boolean) + (userMap.containsKey(JsonKey.EMAIL_VERIFIED) + ? userMap.get(JsonKey.EMAIL_VERIFIED) + : userDbRecord.get(JsonKey.EMAIL_VERIFIED)); + boolean phoneVerified = + (boolean) + (userMap.containsKey(JsonKey.PHONE_VERIFIED) + ? userMap.get(JsonKey.PHONE_VERIFIED) + : userDbRecord.get(JsonKey.PHONE_VERIFIED)); + // for existing users, it won't contain state-validation + // adding in release-2.4.0 + // userDbRecord- record from es. + if (!userDbRecord.containsKey(JsonKey.STATE_VALIDATED)) { + setStateValidation(userDbRecord, userBooleanMap); + } else { + userBooleanMap.put( + JsonKey.STATE_VALIDATED, (boolean) userDbRecord.get(JsonKey.STATE_VALIDATED)); + } + userBooleanMap.put(JsonKey.EMAIL_VERIFIED, emailVerified); + userBooleanMap.put(JsonKey.PHONE_VERIFIED, phoneVerified); + return userBooleanMap; + } + + + /** This method set the default value of the user-flag if it is not present in userDbRecord + * @param userDbRecord + * @param flagType + * @param verifiedFlagType + * @return + */ + public Map setUserFlagValue(Map userDbRecord, String flagType, String verifiedFlagType) { + if(userDbRecord.get(flagType) != null && (userDbRecord.get(verifiedFlagType) == null + || (boolean)userDbRecord.get(verifiedFlagType))) { + userDbRecord.put(verifiedFlagType, true); + } else { + userDbRecord.put(verifiedFlagType, false); + } + return userDbRecord; + } + + private String getCustodianRootOrgId() { + String custodianChannel = + userService.getCustodianChannel(new HashMap<>(), systemSettingActorRef); + return userService.getRootOrgIdFromChannel(custodianChannel); + } + @SuppressWarnings("unchecked") private void convertValidatedLocationCodesToIDs(Map userMap) { if (userMap.containsKey(JsonKey.LOCATION_CODES) @@ -776,4 +863,64 @@ private static void handleGetFrameworkDetails(String frameworkId) { } } } -} + + private void throwRecoveryParamsMatchException(String type, String recoveryType) { + ProjectLogger.log( + "UserManagementActor:throwParamMatchException:".concat(recoveryType + "") + + "should not same as primary ".concat(type + ""), + LoggerEnum.ERROR.name()); + ProjectCommonException.throwClientErrorException( + ResponseCode.recoveryParamsMatchException, + MessageFormat.format( + ResponseCode.recoveryParamsMatchException.getErrorMessage(), recoveryType, type)); + } + + private void validateRecoveryEmailPhone( + Map userDbRecord, Map userReqMap) { + String userPrimaryPhone = (String) userDbRecord.get(JsonKey.PHONE); + String userPrimaryEmail = (String) userDbRecord.get(JsonKey.EMAIL); + String recoveryEmail = (String) userReqMap.get(JsonKey.RECOVERY_EMAIL); + String recoveryPhone = (String) userReqMap.get(JsonKey.RECOVERY_PHONE); + if (StringUtils.isNotBlank(recoveryEmail) + && Matcher.matchIdentifiers(userPrimaryEmail, recoveryEmail)) { + throwRecoveryParamsMatchException(JsonKey.EMAIL, JsonKey.RECOVERY_EMAIL); + } + if (StringUtils.isNotBlank(recoveryPhone) + && Matcher.matchIdentifiers(userPrimaryPhone, recoveryPhone)) { + throwRecoveryParamsMatchException(JsonKey.PHONE, JsonKey.RECOVERY_PHONE); + } + validatePrimaryEmailOrPhone(userDbRecord, userReqMap); + validatePrimaryAndRecoveryKeys(userReqMap); + } + + private void validatePrimaryEmailOrPhone( + Map userDbRecord, Map userReqMap) { + String userPrimaryPhone = (String) userReqMap.get(JsonKey.PHONE); + String userPrimaryEmail = (String) userReqMap.get(JsonKey.EMAIL); + String recoveryEmail = (String) userDbRecord.get(JsonKey.RECOVERY_EMAIL); + String recoveryPhone = (String) userDbRecord.get(JsonKey.RECOVERY_PHONE); + if (StringUtils.isNotBlank(userPrimaryEmail) + && Matcher.matchIdentifiers(userPrimaryEmail, recoveryEmail)) { + throwRecoveryParamsMatchException(JsonKey.EMAIL, JsonKey.RECOVERY_EMAIL); + } + if (StringUtils.isNotBlank(userPrimaryPhone) + && Matcher.matchIdentifiers(userPrimaryPhone, recoveryPhone)) { + throwRecoveryParamsMatchException(JsonKey.PHONE, JsonKey.RECOVERY_PHONE); + } + } + + private void validatePrimaryAndRecoveryKeys(Map userReqMap) { + String userPhone = (String) userReqMap.get(JsonKey.PHONE); + String userEmail = (String) userReqMap.get(JsonKey.EMAIL); + String userRecoveryEmail = (String) userReqMap.get(JsonKey.RECOVERY_EMAIL); + String userRecoveryPhone = (String) userReqMap.get(JsonKey.RECOVERY_PHONE); + if (StringUtils.isNotBlank(userEmail) + && Matcher.matchIdentifiers(userEmail, userRecoveryEmail)) { + throwRecoveryParamsMatchException(JsonKey.EMAIL, JsonKey.RECOVERY_EMAIL); + } + if (StringUtils.isNotBlank(userPhone) + && Matcher.matchIdentifiers(userPhone, userRecoveryPhone)) { + throwRecoveryParamsMatchException(JsonKey.PHONE, JsonKey.RECOVERY_PHONE); + } + } +} \ No newline at end of file diff --git a/actors/user/src/main/java/org/sunbird/user/actors/UserMergeActor.java b/actors/user/src/main/java/org/sunbird/user/actors/UserMergeActor.java index 39d4bb715..8b9334433 100644 --- a/actors/user/src/main/java/org/sunbird/user/actors/UserMergeActor.java +++ b/actors/user/src/main/java/org/sunbird/user/actors/UserMergeActor.java @@ -40,7 +40,6 @@ ) public class UserMergeActor extends UserBaseActor { String topic = null; - String BOOTSTRAP_SERVERS = null; Producer producer = null; private ObjectMapper objectMapper = new ObjectMapper(); private UserService userService = UserServiceImpl.getInstance(); @@ -275,21 +274,14 @@ private void checkTokenDetails(Map headers, String mergeeId, String mergerId) { /** Initialises Kafka producer required for dispatching messages on Kafka. */ private void initKafkaClient() { + ProjectLogger.log("UserMergeActor:initKafkaClient: starts = ", LoggerEnum.INFO.name()); Config config = ConfigUtil.getConfig(); - BOOTSTRAP_SERVERS = - config.getString(KafkaConfigConstants.SUNBIRD_USER_CERT_KAFKA_SERVICE_CONFIG); topic = config.getString(KafkaConfigConstants.SUNBIRD_USER_CERT_KAFKA_TOPIC); - - ProjectLogger.log( - "KafkaTelemetryDispatcherActor:initKafkaClient: Bootstrap servers = " + BOOTSTRAP_SERVERS, - LoggerEnum.INFO.name()); ProjectLogger.log("UserMergeActor:initKafkaClient: topic = " + topic, LoggerEnum.INFO.name()); try { - producer = - KafkaClient.createProducer( - BOOTSTRAP_SERVERS, KafkaConfigConstants.KAFKA_CLIENT_USER_CERT_PRODUCER); + producer = KafkaClient.getProducer(); } catch (Exception e) { - ProjectLogger.log("UserMergeActor:initKafkaClient: An exception occurred.", e); + ProjectLogger.log("UserMergeActor:initKafkaClient: An exception occurred." +e , LoggerEnum.ERROR.name()); } } -} +} \ No newline at end of file diff --git a/actors/user/src/main/java/org/sunbird/user/service/UserService.java b/actors/user/src/main/java/org/sunbird/user/service/UserService.java index f2e72e2d7..a708a6543 100644 --- a/actors/user/src/main/java/org/sunbird/user/service/UserService.java +++ b/actors/user/src/main/java/org/sunbird/user/service/UserService.java @@ -38,5 +38,6 @@ void syncUserProfile( List getEncryptedList(List dataList); boolean checkUsernameUniqueness(String username, boolean isEncrypted); + String getCustodianOrgId(ActorRef actorRef); -} +} \ No newline at end of file diff --git a/actors/user/src/main/java/org/sunbird/user/service/impl/UserServiceImpl.java b/actors/user/src/main/java/org/sunbird/user/service/impl/UserServiceImpl.java index bcf3e31bf..2a5c5dfc5 100644 --- a/actors/user/src/main/java/org/sunbird/user/service/impl/UserServiceImpl.java +++ b/actors/user/src/main/java/org/sunbird/user/service/impl/UserServiceImpl.java @@ -31,6 +31,7 @@ import org.sunbird.common.responsecode.ResponseCode; import org.sunbird.dto.SearchDTO; import org.sunbird.helper.ServiceFactory; +import org.sunbird.learner.util.DataCacheHandler; import org.sunbird.learner.util.Util; import org.sunbird.models.systemsetting.SystemSetting; import org.sunbird.models.user.User; @@ -259,11 +260,18 @@ public String getCustodianChannel(Map userMap, ActorRef actorRef String channel = (String) userMap.get(JsonKey.CHANNEL); if (StringUtils.isBlank(channel)) { try { - SystemSettingClient client = SystemSettingClientImpl.getInstance(); - SystemSetting systemSetting = - client.getSystemSettingByField(actorRef, JsonKey.CUSTODIAN_ORG_CHANNEL); - if (null != systemSetting && StringUtils.isNotBlank(systemSetting.getValue())) { - channel = systemSetting.getValue(); + Map configSettingMap = DataCacheHandler.getConfigSettings(); + channel = configSettingMap.get(JsonKey.CUSTODIAN_ORG_CHANNEL); + if (StringUtils.isBlank(channel)) { + SystemSettingClient client = SystemSettingClientImpl.getInstance(); + SystemSetting custodianOrgChannelSetting = + client.getSystemSettingByField( + actorRef, + JsonKey.CUSTODIAN_ORG_CHANNEL); + if (custodianOrgChannelSetting != null && StringUtils.isNotBlank(custodianOrgChannelSetting.getValue())) { + configSettingMap.put(custodianOrgChannelSetting.getId(), custodianOrgChannelSetting.getValue()); + channel = custodianOrgChannelSetting.getValue(); + } } } catch (Exception ex) { ProjectLogger.log( diff --git a/actors/user/src/test/java/org/sunbird/user/UserFrameworkTest.java b/actors/user/src/test/java/org/sunbird/user/UserFrameworkTest.java index 621820663..47ec8b81d 100644 --- a/actors/user/src/test/java/org/sunbird/user/UserFrameworkTest.java +++ b/actors/user/src/test/java/org/sunbird/user/UserFrameworkTest.java @@ -119,6 +119,7 @@ private Request getRequest(String key, String value) { request.put(JsonKey.USER, innerMap); request.put(JsonKey.USER_ID, userId); request.put(JsonKey.FRAMEWORK, frameworkMap); + getUpdateRequestWithDefaultFlags(request); reqObj.setRequest(request); Map context = new HashMap<>(); context.put(JsonKey.REQUESTED_BY, "someValue"); diff --git a/actors/user/src/test/java/org/sunbird/user/UserManagementActorTest.java b/actors/user/src/test/java/org/sunbird/user/UserManagementActorTest.java index 0e174ebf0..d0509a3d5 100644 --- a/actors/user/src/test/java/org/sunbird/user/UserManagementActorTest.java +++ b/actors/user/src/test/java/org/sunbird/user/UserManagementActorTest.java @@ -10,6 +10,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; + import org.junit.Test; import org.mockito.Mockito; import org.sunbird.actorutil.InterServiceCommunicationFactory; @@ -20,6 +21,7 @@ import org.sunbird.learner.util.DataCacheHandler; import scala.concurrent.Promise; + public class UserManagementActorTest extends UserManagementActorTestBase { @Test @@ -164,10 +166,11 @@ true, true, true, getUpdateRequestWithLocationCodes(), ActorOperations.UPDATE_US @Test public void testUpdateUserSuccess() { - + Map req = getExternalIdMap(); + getUpdateRequestWithDefaultFlags(req); boolean result = testScenario( - getRequest(true, true, true, getExternalIdMap(), ActorOperations.UPDATE_USER), null); + getRequest(true, true, true, req, ActorOperations.UPDATE_USER), null); assertTrue(result); } @@ -190,10 +193,11 @@ true, true, true, getUpdateRequestWithLocationCodes(), ActorOperations.UPDATE_US @Test public void testUpdateUserSuccessWithoutUserCallerId() { - + Map req = getExternalIdMap(); + getUpdateRequestWithDefaultFlags(req); boolean result = testScenario( - getRequest(false, true, true, getExternalIdMap(), ActorOperations.UPDATE_USER), null); + getRequest(false, true, true, req, ActorOperations.UPDATE_USER), null); assertTrue(result); } @@ -237,6 +241,7 @@ public void testCreateUserFailureWithUserTypeAsTeacherAndCustodianOrg() { @Test public void testUpdateUserSuccessWithUserTypeTeacher() { Map req = getExternalIdMap(); + getUpdateRequestWithDefaultFlags(req); req.put(JsonKey.USER_TYPE, JsonKey.TEACHER); when(userService.getUserById(Mockito.anyString())).thenReturn(getUser(false)); when(userService.getRootOrgIdFromChannel(Mockito.anyString())).thenReturn("rootOrgId1"); @@ -328,6 +333,7 @@ public void testUpdateUserOrgFailureWithEmptyRolesReqPrivateApi() { @Test public void testUpdateUserOrgSuccessPrivateApi() { Map req = getUserOrgUpdateRequest(true); + getUpdateRequestWithDefaultFlags(req); Request request = getRequest(false, false, true, req, ActorOperations.UPDATE_USER); request.getContext().put(JsonKey.PRIVATE, true); mockForUserOrgUpdate(); @@ -338,6 +344,7 @@ public void testUpdateUserOrgSuccessPrivateApi() { @Test public void testUpdateUserOrgSuccessWithoutRolesPrivateApi() { Map req = getUserOrgUpdateRequest(true); + getUpdateRequestWithDefaultFlags(req); Request request = getRequest(false, false, true, req, ActorOperations.UPDATE_USER); request.getContext().put(JsonKey.PRIVATE, true); mockForUserOrgUpdate(); @@ -355,4 +362,4 @@ public void testUpdateUserOrgFailureWithInvalidRolesPrivateApi() { boolean result = testScenario(request, ResponseCode.invalidRole); assertTrue(result); } -} +} \ No newline at end of file diff --git a/actors/user/src/test/java/org/sunbird/user/UserManagementActorTestBase.java b/actors/user/src/test/java/org/sunbird/user/UserManagementActorTestBase.java index f41c0fb6d..9d951714a 100644 --- a/actors/user/src/test/java/org/sunbird/user/UserManagementActorTestBase.java +++ b/actors/user/src/test/java/org/sunbird/user/UserManagementActorTestBase.java @@ -128,6 +128,7 @@ public void beforeEachTest() { UserUtil.setUserDefaultValue(Mockito.anyMap(), Mockito.anyString()); Map requestMap = new HashMap<>(); + requestMap.put(JsonKey.ROOT_ORG_ID, "rootOrgId"); requestMap.put(JsonKey.TNC_ACCEPTED_ON, 12345678L); when(UserUtil.encryptUserData(Mockito.anyMap())).thenReturn(requestMap); PowerMockito.mockStatic(DataCacheHandler.class); @@ -259,6 +260,14 @@ public Map getUpdateRequestWithLocationCodes() { Map reqObj = new HashMap(); reqObj.put(JsonKey.LOCATION_CODES, Arrays.asList("locationCode")); reqObj.put(JsonKey.USER_ID, "userId"); + getUpdateRequestWithDefaultFlags(reqObj); + return reqObj; + } + + public Map getUpdateRequestWithDefaultFlags(Map reqObj) { + reqObj.put(JsonKey.EMAIL_VERIFIED, false); + reqObj.put(JsonKey.PHONE_VERIFIED, false); + reqObj.put(JsonKey.STATE_VALIDATED, false); return reqObj; } diff --git a/actors/user/src/test/java/org/sunbird/user/UserProfileReadActorTest.java b/actors/user/src/test/java/org/sunbird/user/UserProfileReadActorTest.java index dfc5fabbd..316c008be 100644 --- a/actors/user/src/test/java/org/sunbird/user/UserProfileReadActorTest.java +++ b/actors/user/src/test/java/org/sunbird/user/UserProfileReadActorTest.java @@ -131,6 +131,7 @@ public void beforeEachTest() { Map requestMap = new HashMap<>(); requestMap.put(JsonKey.TNC_ACCEPTED_ON, 12345678L); + requestMap.put(JsonKey.ROOT_ORG_ID, "rootOrgId"); when(UserUtil.encryptUserData(Mockito.anyMap())).thenReturn(requestMap); PowerMockito.mockStatic(DataCacheHandler.class); when(ssoManager.getUsernameById(Mockito.anyString())).thenReturn(VALID_USERNAME); diff --git a/service/src/main/java/org/sunbird/middleware/Application.java b/service/src/main/java/org/sunbird/middleware/Application.java index 19ddbbf01..d6b1f4aa9 100644 --- a/service/src/main/java/org/sunbird/middleware/Application.java +++ b/service/src/main/java/org/sunbird/middleware/Application.java @@ -10,15 +10,13 @@ public class Application { public static void main(String[] args) { SunbirdMWService.init(); - checkCassandraConnection(); + checkCassandraConnection(); } public static void checkCassandraConnection() { Util.checkCassandraDbConnections(JsonKey.SUNBIRD); Util.checkCassandraDbConnections(JsonKey.SUNBIRD_PLUGIN); SchedulerManager.schedule(); - // scheduler should start after few minutes so internally it is sleeping for 4 minute , so - // putting in seperate thread . new Thread( new Runnable() { @Override diff --git a/service/src/main/resources/application.conf b/service/src/main/resources/application.conf index 0073acb32..d759ef5c7 100644 --- a/service/src/main/resources/application.conf +++ b/service/src/main/resources/application.conf @@ -620,7 +620,7 @@ SunbirdMWSystem { router = smallest-mailbox-pool nr-of-instances = 2 dispatcher = rr-usr-dispatcher - } + } "/RequestRouter/*/TenantMigrationActor" { router = smallest-mailbox-pool @@ -651,6 +651,12 @@ SunbirdMWSystem { nr-of-instances = 2 dispatcher = rr-usr-dispatcher } + "/RequestRouter/*/UserBulkMigrationActor" + { + router = smallest-mailbox-pool + nr-of-instances = 2 + dispatcher = rr-usr-dispatcher + } } } remote {