Overview
The User Service provides the capabilities of creating a user, searching for a user and retrieving the details of a user. This module will search for a user and if not found, create that user with the user service.
DIGIT's user service masks PII that gets stored in the database using the Encryption Service.
Create a class by the name of UserService under service folder and add the following content to it:
package digit.service;
import digit.config.BTRConfiguration;
import digit.util.UserUtil;
import digit.web.models.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.egov.common.contract.request.RequestInfo;
import org.egov.common.contract.request.User;
import org.egov.common.contract.user.CreateUserRequest;
import org.egov.common.contract.user.UserDetailResponse;
import org.egov.common.contract.user.UserSearchRequest;
import org.egov.tracer.model.CustomException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
@Service
@Slf4j
public class UserService {
private UserUtil userUtils;
private BTRConfiguration config;
@Autowired
public UserService(UserUtil userUtils, BTRConfiguration config) {
this.userUtils = userUtils;
this.config = config;
}
/**
* Calls user service to enrich user from search or upsert user
* @param request
*/
public void callUserService(BirthRegistrationRequest request){
request.getBirthRegistrationApplications().forEach(application -> {
if(!StringUtils.isEmpty(application.getFather().getUuid()))
enrichUser(application, request.getRequestInfo());
else {
User user = createFatherUser(application);
application.getFather().setUuid(upsertUser(user, request.getRequestInfo()).getUuid());
}
});
request.getBirthRegistrationApplications().forEach(application -> {
if(!StringUtils.isEmpty(application.getMother().getUuid()))
enrichUser(application, request.getRequestInfo());
else {
User user = createMotherUser(application);
application.getMother().setUuid(upsertUser(user, request.getRequestInfo()).getUuid());
}
});
}
private User createFatherUser(BirthRegistrationApplication application){
User father = application.getFather();
User user = User.builder().userName(father.getUserName())
.name(father.getName())
.userName((father.getUserName()))
.mobileNumber(father.getMobileNumber())
.emailId(father.getEmailId())
.tenantId(father.getTenantId())
.type(father.getType())
.roles(father.getRoles())
.build();
return user;
}
private User createMotherUser(BirthRegistrationApplication application){
User mother = application.getMother();
User user = User.builder().userName(mother.getUserName())
.name(mother.getName())
.userName((mother.getUserName()))
.mobileNumber(mother.getMobileNumber())
.emailId(mother.getEmailId())
.tenantId(mother.getTenantId())
.type(mother.getType())
.roles(mother.getRoles())
.build();
return user;
}
private User upsertUser(User user, RequestInfo requestInfo){
String tenantId = user.getTenantId();
User userServiceResponse = null;
// Search on mobile number as user name
UserDetailResponse userDetailResponse = searchUser(userUtils.getStateLevelTenant(tenantId),null, user.getUserName());
if (!userDetailResponse.getUser().isEmpty()) {
User userFromSearch = userDetailResponse.getUser().get(0);
log.info(userFromSearch.toString());
if(!user.getUserName().equalsIgnoreCase(userFromSearch.getUserName())){
userServiceResponse = updateUser(requestInfo,user,userFromSearch);
}
else userServiceResponse = userDetailResponse.getUser().get(0);
}
else {
userServiceResponse = createUser(requestInfo,tenantId,user);
}
// Enrich the accountId
// user.setId(userServiceResponse.getUuid());
return userServiceResponse;
}
private void enrichUser(BirthRegistrationApplication application, RequestInfo requestInfo){
String accountIdFather = application.getFather().getUuid();
String accountIdMother = application.getMother().getUuid();
String tenantId = application.getTenantId();
UserDetailResponse userDetailResponseFather = searchUser(userUtils.getStateLevelTenant(tenantId),accountIdFather,null);
UserDetailResponse userDetailResponseMother = searchUser(userUtils.getStateLevelTenant(tenantId),accountIdMother,null);
if(userDetailResponseFather.getUser().isEmpty())
throw new CustomException("INVALID_ACCOUNTID","No user exist for the given accountId");
else application.getFather().setUuid(userDetailResponseFather.getUser().get(0).getUuid());
if(userDetailResponseMother.getUser().isEmpty())
throw new CustomException("INVALID_ACCOUNTID","No user exist for the given accountId");
else application.getMother().setUuid(userDetailResponseMother.getUser().get(0).getUuid());
}
/**
* Creates the user from the given userInfo by calling user service
* @param requestInfo
* @param tenantId
* @param userInfo
* @return
*/
private User createUser(RequestInfo requestInfo,String tenantId, User userInfo) {
userUtils.addUserDefaultFields(userInfo.getMobileNumber(),tenantId, userInfo);
StringBuilder uri = new StringBuilder(config.getUserHost())
.append(config.getUserContextPath())
.append(config.getUserCreateEndpoint());
CreateUserRequest user = new CreateUserRequest(requestInfo, userInfo);
log.info(user.getUser().toString());
UserDetailResponse userDetailResponse = userUtils.userCall(user, uri);
return userDetailResponse.getUser().get(0);
}
/**
* Updates the given user by calling user service
* @param requestInfo
* @param user
* @param userFromSearch
* @return
*/
private User updateUser(RequestInfo requestInfo,User user,User userFromSearch) {
userFromSearch.setName(user.getName());
StringBuilder uri = new StringBuilder(config.getUserHost())
.append(config.getUserContextPath())
.append(config.getUserUpdateEndpoint());
UserDetailResponse userDetailResponse = userUtils.userCall(new CreateUserRequest(requestInfo, userFromSearch), uri);
return userDetailResponse.getUser().get(0);
}
/**
* calls the user search API based on the given accountId and userName
* @param stateLevelTenant
* @param accountId
* @param userName
* @return
*/
public UserDetailResponse searchUser(String stateLevelTenant, String accountId, String userName){
UserSearchRequest userSearchRequest =new UserSearchRequest();
userSearchRequest.setActive(false);
// userSearchRequest.setUserType("CITIZEN");
userSearchRequest.setTenantId(stateLevelTenant);
if(StringUtils.isEmpty(accountId) && StringUtils.isEmpty(userName))
return null;
if(!StringUtils.isEmpty(accountId))
userSearchRequest.setUuid(Collections.singletonList(accountId));
if(!StringUtils.isEmpty(userName))
userSearchRequest.setUserName(userName);
StringBuilder uri = new StringBuilder(config.getUserHost()).append(config.getUserSearchEndpoint());
return userUtils.userCall(userSearchRequest,uri);
}
/**
* calls the user search API based on the given list of user uuids
* @param uuids
* @return
*/
private Map<String,User> searchBulkUser(List<String> uuids){
UserSearchRequest userSearchRequest =new UserSearchRequest();
userSearchRequest.setActive(false);
userSearchRequest.setUserType("CITIZEN");
if(!CollectionUtils.isEmpty(uuids))
userSearchRequest.setUuid(uuids);
StringBuilder uri = new StringBuilder(config.getUserHost()).append(config.getUserSearchEndpoint());
UserDetailResponse userDetailResponse = userUtils.userCall(userSearchRequest,uri);
List<User> users = userDetailResponse.getUser();
if(CollectionUtils.isEmpty(users))
throw new CustomException("USER_NOT_FOUND","No user found for the uuids");
Map<String,User> idToUserMap = users.stream().collect(Collectors.toMap(User::getUuid, Function.identity()));
return idToUserMap;
}
}
package digit.util;
import com.fasterxml.jackson.databind.ObjectMapper;
import digit.config.Configuration;
import static digit.config.ServiceConstants.*;
import org.egov.common.contract.request.Role;
import org.egov.common.contract.request.User;
import org.egov.common.contract.user.UserDetailResponse;
import org.egov.common.contract.user.enums.UserType;
import digit.repository.ServiceRequestRepository;
import org.egov.tracer.model.CustomException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
@Component
public class UserUtil {
@Autowired
private ObjectMapper mapper;
@Autowired
private ServiceRequestRepository serviceRequestRepository;
@Autowired
private Configuration configs;
@Autowired
public UserUtil(ObjectMapper mapper, ServiceRequestRepository serviceRequestRepository) {
this.mapper = mapper;
this.serviceRequestRepository = serviceRequestRepository;
}
/**
* Returns UserDetailResponse by calling user service with given uri and object
* @param userRequest Request object for user service
* @param uri The address of the endpoint
* @return Response from user service as parsed as userDetailResponse
*/
public UserDetailResponse userCall(Object userRequest, StringBuilder uri) {
String dobFormat = null;
if(uri.toString().contains(configs.getUserSearchEndpoint()) || uri.toString().contains(configs.getUserUpdateEndpoint()))
dobFormat=DOB_FORMAT_Y_M_D;
else if(uri.toString().contains(configs.getUserCreateEndpoint()))
dobFormat = DOB_FORMAT_D_M_Y;
try{
LinkedHashMap responseMap = (LinkedHashMap)serviceRequestRepository.fetchResult(uri, userRequest);
parseResponse(responseMap,dobFormat);
UserDetailResponse userDetailResponse = mapper.convertValue(responseMap,UserDetailResponse.class);
return userDetailResponse;
}
catch(IllegalArgumentException e)
{
throw new CustomException(ILLEGAL_ARGUMENT_EXCEPTION_CODE,OBJECTMAPPER_UNABLE_TO_CONVERT);
}
}
/**
* Parses date formats to long for all users in responseMap
* @param responseMap LinkedHashMap got from user api response
*/
public void parseResponse(LinkedHashMap responseMap, String dobFormat){
List<LinkedHashMap> users = (List<LinkedHashMap>)responseMap.get(USER);
String format1 = DOB_FORMAT_D_M_Y_H_M_S;
if(users!=null){
users.forEach( map -> {
map.put(CREATED_DATE,dateTolong((String)map.get(CREATED_DATE),format1));
if((String)map.get(LAST_MODIFIED_DATE)!=null)
map.put(LAST_MODIFIED_DATE,dateTolong((String)map.get(LAST_MODIFIED_DATE),format1));
if((String)map.get(DOB)!=null)
map.put(DOB,dateTolong((String)map.get(DOB),dobFormat));
if((String)map.get(PWD_EXPIRY_DATE)!=null)
map.put(PWD_EXPIRY_DATE,dateTolong((String)map.get(PWD_EXPIRY_DATE),format1));
}
);
}
}
/**
* Converts date to long
* @param date date to be parsed
* @param format Format of the date
* @return Long value of date
*/
private Long dateTolong(String date,String format){
SimpleDateFormat f = new SimpleDateFormat(format);
Date d = null;
try {
d = f.parse(date);
} catch (ParseException e) {
throw new CustomException(INVALID_DATE_FORMAT_CODE,INVALID_DATE_FORMAT_MESSAGE);
}
return d.getTime();
}
/**
* enriches the userInfo with statelevel tenantId and other fields
* The function creates user with username as mobile number.
* @param mobileNumber
* @param tenantId
* @param userInfo
*/
public void addUserDefaultFields(String mobileNumber,String tenantId, User userInfo){
Role role = getCitizenRole(tenantId);
userInfo.setMobileNumber(mobileNumber);
userInfo.setTenantId(getStateLevelTenant(tenantId));
userInfo.setType("CITIZEN");
}
/**
* Returns role object for citizen
* @param tenantId
* @return
*/
private Role getCitizenRole(String tenantId){
Role role = Role.builder().build();
role.setCode(CITIZEN_UPPER);
role.setName(CITIZEN_LOWER);
role.setTenantId(getStateLevelTenant(tenantId));
return role;
}
public String getStateLevelTenant(String tenantId){
return tenantId.split("\\.")[0];
}
}
Add the below methods to the enrichment class we created. When we search for an application, the code below will search for the users associated with the application and add in their details to the response object.
public void enrichFatherApplicantOnSearch(BirthRegistrationApplication application) {
UserDetailResponse fatherUserResponse = userService.searchUser(userUtils.getStateLevelTenant(application.getTenantId()),application.getFather().getUuid(),null);
User fatherUser = fatherUserResponse.getUser().get(0);
log.info(fatherUser.toString());
User fatherApplicant = User.builder()
.mobileNumber(fatherUser.getMobileNumber())
.id(fatherUser.getId())
.name(fatherUser.getName())
.userName((fatherUser.getUserName()))
.type(fatherUser.getType())
.roles(fatherUser.getRoles())
.uuid(fatherUser.getUuid()).build();
application.setFather(fatherApplicant);
}
public void enrichMotherApplicantOnSearch(BirthRegistrationApplication application) {
UserDetailResponse motherUserResponse = userService.searchUser(userUtils.getStateLevelTenant(application.getTenantId()),application.getMother().getUuid(),null);
User motherUser = motherUserResponse.getUser().get(0);
log.info(motherUser.toString());
User motherApplicant = User.builder()
.mobileNumber(motherUser.getMobileNumber())
.id(motherUser.getId())
.name(motherUser.getName())
.userName((motherUser.getUserName()))
.type(motherUser.getType())
.roles(motherUser.getRoles())
.uuid(motherUser.getUuid()).build();
application.setMother(motherApplicant);
}
Add in a userService object:
@Autowired
private UserService userService;
And enhance the following two methods in BirthRegistrationService.java:
public List<BirthRegistrationApplication> registerBtRequest(BirthRegistrationRequest birthRegistrationRequest) {
// Validate applications
validator.validateBirthApplication(birthRegistrationRequest);
// Enrich applications
enrichmentUtil.enrichBirthApplication(birthRegistrationRequest);
// Enrich/Upsert user in upon birth registration
userService.callUserService(birthRegistrationRequest);
//
// Initiate workflow for the new application
workflowService.updateWorkflowStatus(birthRegistrationRequest);
// Push the application to the topic for persister to listen and persist
producer.push("save-bt-application", birthRegistrationRequest);
// Return the response back to user
return birthRegistrationRequest.getBirthRegistrationApplications();
}
public List<BirthRegistrationApplication> searchBtApplications(RequestInfo requestInfo, BirthApplicationSearchCriteria birthApplicationSearchCriteria) {
// Fetch applications from database according to the given search criteria
List<BirthRegistrationApplication> applications = birthRegistrationRepository.getApplications(birthApplicationSearchCriteria);
// If no applications are found matching the given criteria, return an empty list
if(CollectionUtils.isEmpty(applications))
return new ArrayList<>();
// Enrich mother and father of applicant objects
applications.forEach(application -> {
enrichmentUtil.enrichFatherApplicantOnSearch(application);
enrichmentUtil.enrichMotherApplicantOnSearch(application);
});
// Otherwise return the found applications
return applications;
}
Add the following properties in application.properties file:
Note: If you're port-forwarding using k8s, use "localhost". Otherwise, if you have a valid auth token, provide the hostname here.
#User config
egov.user.host=http://localhost:8284/
egov.user.context.path=/user/users
egov.user.create.path=/_createnovalidate
egov.user.search.path=/user/_search
egov.user.update.path=/_updatenovalidate