Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

This guide aims at enabling the readers to setup a development environment on their system, develop their very own micro-service and integrate/communicate with services running remotely on the sandbox environment.

Prerequisites:

  • Prior Knowledge of Java/J2EE.

  • Prior Knowledge of Spring Boot.

  • Prior Knowledge of REST APIs and related concepts like path parameters, headers, JSON etc.

  • Prior knowledge of Git

  • PostgreSQL

  • Kafka

  • Following services should be up and running( or else should be pointed to sandbox environment):

    • userUser

    • MDMS

    • Persister

    • Location

    • Localization

    • Id-Gen

    • Billing-service

    • URL-shortener

...

*** NOTE - In case you run into an error stating “error: You must be logged in to the server (Unauthorized)”, try to add sudo before the command. For example, “sudo kubectl get pods”. That should resolve the error.

Swagger Documentation & Generating Project from it:

The first step to develop any micro-service starts at preparing a Swagger documentation which details out all the APIs that the service is going to expose which the clients can consume.

...

Code Block
<dependency>

   <groupId>org.flywaydb</groupId>

   <artifactId>flyway-core</artifactId>

</dependency>

<dependency>

   <groupId>org.postgresql</groupId>

   <artifactId>postgresql</artifactId>

   <version>42.2.2.jre7</version>

</dependency>

 

Setting up database connection and adding variables in application.properties

All dependent service host urls and API endpoints should be added in application.properties. Along with it whatever properties can be overwritten during deployment should be part of this file(eg: DB url and passwords,Kafka server properties, control knobs for functionalities  etc.). To remove boilerplate code for referring variables from application.properties, we create a configuration file and autowire this configuration file wherever we need to refer to these variables. Following properties should be added for configuring database and kafka server( Use the default values, in case you want to tune kafka server that can be overwritten during deployment).

...

To add custom properties in application.properties file and then referencing them in your application -

...

Add SQL scripts to create DB using flyway:

Once the database has been configured, for creating tables in postgres DB we will use flyway. The following properties should be configured in application.properties file to enable flyway migration:

Code Block
#FLYWAY CONFIGURATION
spring.flyway.url=jdbc:postgresql://localhost:5432/postgres
spring.flyway.user=postgres
spring.flyway.password=postgres
spring.flyway.table=public
spring.flyway.baseline-on-migrate=true
spring.flyway.outOfOrder=true
spring.flyway.locations=classpath:/db/migration/main
spring.flyway.enabled=true

For adding the flyway scripts the following folder structure should be maintained:

...

Code Block
CREATE TABLE eg_bt_registration(
 id character varying(64),
 tenantId character varying(64),
 babyFirstNameapplicationNumber character varying(64),
 applicationNumberbabyFirstName character varying(64),
 babyLastName character varying(64),
 motherNamefatherName character varying(64),
 fatherNamemotherName character varying(64),
 doctorAttendingBirthdoctorName character varying(64),
 hospitalName character varying(64),
 placeOfBirth character varying(64),
 dateOfBirth bigint,
 createdTime bigint,
 lastModifiedTime bigint,
 CONSTRAINT uk_eg_tlbt_TradeLicenseregistration UNIQUE (id)
);
CREATE TABLE eg_bt_address(
   id character varying(64),
   tenantId character varying(64),
   doorNo character varying(64),
   latitude FLOAT,
   longitude FLOAT,
   buildingName character varying(64),
   addressId character varying(64),
   addressNumber character varying(64),
   type character varying(64),
   addressLine1 character varying(256),
   addressLine2 character varying(256),
   landmark character varying(64),
   street character varying(64),
   city character varying(64),
   locality character varying(64),
   pincode character varying(64),
   detail character varying(64),
   registrationId character varying(64),
   createdBy character varying(64),
   lastModifiedBy character varying(64),
   createdTime bigint,
   lastModifiedTime bigint,
   CONSTRAINT uk_eg_tlbt_address PRIMARY KEY (id),
   CONSTRAINT fk_eg_tlbt_address FOREIGN KEY (registrationId) REFERENCES eg_bt_registration (id)
     ON UPDATE CASCADE
     ON DELETE CASCADE
);

Project Structure:

We maintain the following project service for all microservices -

...

Project structure can also be looked at this link - Git Link


Adding MDMS data:

MDMS data is the master data used by the application. This data is stored as JSON on git in the format given below. For any service delivery typical master data will be the allowed values for certain fields and the taxheads (in case if payment is present in the flow).

...

Once data is added to mdms repository, mdms service has to be restarted which will then load the newly added/updated mdms configs. A sample mdms config file can be viewed here - Sample MDMS data file

 

Adding Workflow configuration:

Workflow configuration should be created based on the business requirements. The configuration can be inserted using the /businessservice/_create API. To create the workflow configuration refer the following documentation: Configuring Workflows For New Product/Entity

...

Code Block
{
   "RequestInfo": {
       "apiId": "Rainmaker",
       "action": "",
       "did": 1,
       "key": "",
       "msgId": "20170310130900|en_IN",
       "requesterId": "",
       "ts": 1513579888683,
       "ver": ".01",
       "authToken": "{{devAuth}}"
   },
   "BusinessServices": [
       {
           "tenantId": "pb",
           "businessService": "BTR",
           "business": "birth-services",
           "businessServiceSla": 432000000,
           "states": [
               {
                   "sla": null,
                   "state": null,
                   "applicationStatus": null,
                   "docUploadRequired": true,
                   "isStartState": true,
                   "isTerminateState": false,
                   "isStateUpdatable": true,
                   "actions": [
                       {
                           "action": "APPLY",
                           "nextState": "APPLIED",
                           "roles": [
                               "CITIZEN",
                               "EMPLOYEE"
                           ]
                       }
                   ]
               },
               {
                   "sla": null,
                   "state": "APPLIED",
                   "applicationStatus": "APPLIED",
                   "docUploadRequired": false,
                   "isStartState": false,
                   "isTerminateState": true,
                   "isStateUpdatable": false,
                   "actions": [
                       {
                           "action": "APPROVE",
                           "nextState": "APPROVED",
                           "roles": [
                               "EMPLOYEE"
                           ]
                       },
                       {
                           "action": "REJECT",
                           "nextState": "REJECTED",
                           "roles": [
                               "EMPLOYEE"
                           ]
                       }
                   ]
               },
               {
                   "sla": null,
                   "state": "APPROVED",
                   "applicationStatus": "APPROVED",
                   "docUploadRequired": false,
                   "isStartState": false,
                   "isTerminateState": false,
                   "isStateUpdatable": false,
                   "actions": [
                       {
                           "action": "PAY",
                           "nextState": "REGISTRATIONCOMPLETED",
                           "roles": [
                               "SYSTEM_PAYMENT",
                               "CITIZEN",
                               "EMPLOYEE"
                           ]
                       }
                   ]
               },
               {
                   "sla": null,
                   "state": "REJECTED",
                   "applicationStatus": "REJECTED",
                   "docUploadRequired": false,
                   "isStartState": false,
                   "isTerminateState": true,
                   "isStateUpdatable": false,
                   "actions": null
               },
               {
                   "sla": null,
                   "state": "REGISTRATIONCOMPLETED",
                   "applicationStatus": "REGISTRATIONCOMPLETED",
                   "docUploadRequired": false,
                   "isStartState": false,
                   "isTerminateState": true,
                   "isStateUpdatable": false,
                   "actions": null
               }
           ]
       }
   ]
}

Import core service models:

Models/POJOs of the dependent service can be imported from digit-core-models library (work on creating library is ongoing). These models will be used in integration with the dependent services.

...

Code Block
@Getter
@Setter 
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class RequestInfoWrapper {

    @JsonProperty("RequestInfo")
    private RequestInfo requestInfo;
}

Update the Applicant POJO to have the following content b) Under config folder, create BTRConfiguration and MainConfiguration classes -

Code Block
@Getter
@Setter@Component
@Data
@Import({TracerConfiguration.class})
@NoArgsConstructor
@AllArgsConstructor
@NoArgsConstructor@Setter
@Builder@Getter
public class Applicant  BTRConfiguration {
        @JsonProperty@Value("id${app.timezone}")
 
      private String timeZone;
id
= null;   @PostConstruct
    public void @JsonPropertyinitialize("babyFirstName") {
        private String babyFirstName = null; TimeZone.setDefault(TimeZone.getTimeZone(timeZone));
    }

    @Bean
    @Autowired
    public MappingJackson2HttpMessageConverter @JsonProperty("babyLastName"jacksonConverter(ObjectMapper objectMapper) {
       private StringMappingJackson2HttpMessageConverter babyLastNameconverter = new nullMappingJackson2HttpMessageConverter();
        converter.setObjectMapper(objectMapper);
        @JsonProperty("password")
return converter;
       private}
String
password = null;  // User Config
     @JsonProperty@Value("salutation${egov.user.host}")
        private String salutation = nulluserHost;

        @JsonProperty("gender")
  @Value("${egov.user.context.path}")
     private String genderuserContextPath;
=
null;    @Value("${egov.user.create.path}")
     @JsonProperty("mobileNumberprivate String userCreateEndpoint;

    @Value("${egov.user.search.path}")
        private String mobileNumber = nulluserSearchEndpoint;

        @JsonProperty("emailId")
  @Value("${egov.user.update.path}")
     private String emailIduserUpdateEndpoint;
=
null;    //Idgen Config
    @JsonProperty@Value("altContactNumber${egov.idgen.host}")
   
    private String altContactNumber = nullidGenHost;

        @JsonProperty@Value("fatherName${egov.idgen.path}")
 
      private String fatherNameidGenPath;
=
null;    //Workflow Config
    @JsonProperty@Value("motherName${egov.workflow.host}")
  
     private String motherNamewfHost;
=
null;    @Value("${egov.workflow.transition.path}")
    private String wfTransitionPath;

     @JsonProperty@Value("doctorAttendingBirth${egov.workflow.businessservice.search.path}")
  
     private String doctorAttendingBirth = nullwfBusinessServiceSearchPath;

        @JsonProperty("permanentAddress")
   @Value("${egov.workflow.processinstance.search.path}")
    private String permanentAddress = nullwfProcessInstanceSearchPath;

        @JsonProperty@Value("permanentCity${is.workflow.enabled}")
    private Boolean isWorkflowEnabled;

private
String permanentCity = null; // BTR Variables

     @JsonProperty@Value("permanentPincode${btr.kafka.create.topic}")
   
    private String permanentPincode = nullcreateTopic;

        @JsonProperty("correspondenceCity@Value("${btr.kafka.update.topic}")
        private String correspondenceCity = nullupdateTopic;

        @JsonProperty@Value("correspondencePincode${btr.default.offset}")
   
    private String correspondencePincode = nullInteger defaultOffset;

        @JsonProperty@Value("correspondenceAddress${btr.default.limit}")
    private Integer defaultLimit;

private String correspondenceAddress = null; @Value("${btr.search.max.limit}")
    private Integer maxLimit;


    //MDMS
    @JsonProperty@Value("hospitalName${egov.mdms.host}")
    private String mdmsHost;

private String hospitalName = null; @Value("${egov.mdms.search.endpoint}")
    private String mdmsEndPoint;

    //HRMS
    @JsonProperty@Value("placeOfBirth${egov.hrms.host}")
   
    private String placeOfBirth = nullhrmsHost;

        @JsonProperty("active@Value("${egov.hrms.search.endpoint}")
        private Boolean active = nullString hrmsEndPoint;

        @JsonProperty("locale@Value("${egov.url.shortner.host}")
    private String urlShortnerHost;

private String locale = null;

        @JsonProperty("type")
  @Value("${egov.url.shortner.endpoint}")
      private String type = nullurlShortnerEndpoint;

        @JsonProperty("signature")
@Value("${egov.sms.notification.topic}")
       private String signature = null;

  smsNotificationTopic;
}

 

Code Block

@Import({TracerConfiguration.class})
public class MainConfiguration {

    @JsonProperty@Value("accountLocked${app.timezone}")
    private String timeZone;

private Boolean accountLocked = null;@PostConstruct
    public void    @JsonProperty("roles"initialize() {
       @Valid
 TimeZone.setDefault(TimeZone.getTimeZone(timeZone));
    }

 private List<Role> roles =@Bean
null;    public ObjectMapper objectMapper(){
   @JsonProperty("fatherOrHusbandName")     return new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES).setTimeZone(TimeZone.getTimeZone(timeZone));
 private String fatherOrHusbandName = null;}

    @Bean
   @JsonProperty("bloodGroup") @Autowired
    public MappingJackson2HttpMessageConverter jacksonConverter(ObjectMapper privateobjectMapper) String{
bloodGroup = null;      MappingJackson2HttpMessageConverter converter = new @JsonPropertyMappingJackson2HttpMessageConverter("identificationMark");
        private String identificationMark = nullconverter.setObjectMapper(objectMapper);
         @JsonProperty("photo")return converter;
    }
   private String photo = null;

        @JsonProperty("createdBy")
        private Long createdBy = null;

        @JsonProperty("createdDate")
        private LocalDate createdDate = null;

        @JsonProperty("lastModifiedBy")
        private Long lastModifiedBy = null;

        @JsonProperty("lastModifiedDate")
        private LocalDate lastModifiedDate = null;

        @JsonProperty("otpReference")
        private String otpReference = null;

        @JsonProperty("tenantId")
        private String tenantId = null;


        public Applicant addRolesItem(Role rolesItem) {
            if (this.roles == null) {
            this.roles = new ArrayList<>();
            }
        this.roles.add(rolesItem);
        return this;
        }

}

b) Under config folder, create BTRConfiguration and MainConfiguration classes -

Code Block
@Component
@Data
@Import({TracerConfiguration.class})
@NoArgsConstructor
@AllArgsConstructor
public class BTRConfiguration {
    @Value("${app.timezone}")
    private String timeZone;

    @PostConstruct
    public void initialize() {
        TimeZone.setDefault(TimeZone.getTimeZone(timeZone));
    }

    @Bean
    @Autowired
    public MappingJackson2HttpMessageConverter jacksonConverter(ObjectMapper objectMapper) {
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        converter.setObjectMapper(objectMapper);
        return converter;
    }

    // User Config
    @Value("${egov.user.host}")
    private String userHost;

    @Value("${egov.user.context.path}")
    private String userContextPath;

    @Value("${egov.user.create.path}")
    private String userCreateEndpoint;

    @Value("${egov.user.search.path}")
    private String userSearchEndpoint;

    @Value("${egov.user.update.path}")
    private String userUpdateEndpoint;

    //Idgen Config
    @Value("${egov.idgen.host}")
    private String idGenHost;

    @Value("${egov.idgen.path}")
    private String idGenPath;

    //Workflow Config
    @Value("${egov.workflow.host}")
    private String wfHost;

    @Value("${egov.workflow.transition.path}")
    private String wfTransitionPath;

    @Value("${egov.workflow.businessservice.search.path}")
    private String wfBusinessServiceSearchPath;

    @Value("${egov.workflow.processinstance.search.path}")
    private String wfProcessInstanceSearchPath;

    @Value("${is.workflow.enabled}")
    private Boolean isWorkflowEnabled;


    // BTR Variables

    @Value("${btr.kafka.create.topic}")
    private String createTopic;

    @Value("${btr.kafka.update.topic}")
    private String updateTopic;

    @Value("${btr.default.offset}")
    private Integer defaultOffset;

    @Value("${btr.default.limit}")
    private Integer defaultLimit;

    @Value("${btr.search.max.limit}")
    private Integer maxLimit;


    //MDMS
    @Value("${egov.mdms.host}")
    private String mdmsHost;

    @Value("${egov.mdms.search.endpoint}")
    private String mdmsEndPoint;

    //HRMS
    @Value("${egov.hrms.host}")
    private String hrmsHost;

    @Value("${egov.hrms.search.endpoint}")
    private String hrmsEndPoint;


    @Value("${egov.url.shortner.host}")
    private String urlShortnerHost;

    @Value("${egov.url.shortner.endpoint}")
    private String urlShortnerEndpoint;

    @Value("${egov.sms.notification.topic}")
    private String smsNotificationTopic;

}

 

Code Block
@Import({TracerConfiguration.class})
public class MainConfiguration {

    @Value("${app.timezone}")
    private String timeZone;

    @PostConstruct
    public void initialize() {
        TimeZone.setDefault(TimeZone.getTimeZone(timeZone));
    }

    @Bean
    public ObjectMapper objectMapper(){
      return new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES).setTimeZone(TimeZone.getTimeZone(timeZone));
    }

    @Bean
    @Autowired
    public MappingJackson2HttpMessageConverter jacksonConverter(ObjectMapper objectMapper) {
      MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
      converter.setObjectMapper(objectMapper);
      return converter;
    }
}
}

Controller Layer:

Controller Layer contains the REST API endpoints which the service wants to expose. The code flow will start from this class. Controller class should be marked with @RestController annotation. 

...

Code Block
@Data
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
@ToString
public class BirthApplicationSearchCriteria {

    @JsonProperty("tenantId")
    private String tenantId;

    @JsonProperty("status")
    private String status;

    @JsonProperty("ids")
    private List<Long>List<String> ids;

    @JsonProperty("applicationNumber")
    private String applicationNumber;

}

...

Code Block
@Component
public class ResponseInfoFactory {

    public ResponseInfo createResponseInfoFromRequestInfo(final RequestInfo requestInfo, final Boolean success) {

        final String apiId = requestInfo != null ? requestInfo.getApiId() : "";
        final String ver = requestInfo != null ? requestInfo.getVer() : "";
        Long ts = null;
        if(requestInfo!=null)
            ts = requestInfo.getTs();
        final String resMsgId = "uief87324";
        final String msgId = requestInfo != null ? requestInfo.getMsgId() : "";
        final String responseStatus = success ? "successful" : "failed";

        return ResponseInfo.builder().apiId(apiId).ver(ver).ts(ts).resMsgId(resMsgId).msgId(msgId).resMsgId(resMsgId)
                .status(responseStatus).build();
    }

}

Service Layer:

Request handlers in the Controller layer call upon the methods defined in the Service layer to perform business logic on the RequestData and prepare the Response to be returned back to the client.

...

Code Block
@Service
public class BirthRegistrationService {

    @Autowired
    private BirthApplicationValidator validator;

    @Autowired
    private BirthApplicationEnrichment enrichmentUtil;

    @Autowired
    private UserService userService;

    @Autowired
    private WorkflowService workflowService;

    @Autowired
    private BirthRegistrationRepository birthRegistrationRepository;

    @Autowired
    private Producer producer;

    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 = BirthRegistrationRepositorybirthRegistrationRepository.getApplications(birthApplicationSearchCriteria);

        // If no applications are found matching the given criteria, return an empty list
        if(CollectionUtils.isEmpty(applications))
            return new ArrayList<>();

        // Otherwise return the found applications
        return applications;
    }

    public BirthRegistrationApplication updateBtApplication(BirthRegistrationRequest birthRegistrationRequest) {
        // Validate whether the application that is being requested for update indeed exists
        BirthRegistrationApplication existingApplication = validator.validateApplicationExistence(birthRegistrationRequest.getBirthRegistrationApplications().get(0));
        existingApplication.setWorkflow(birthRegistrationRequest.getBirthRegistrationApplications().get(0).getWorkflow());
        birthRegistrationRequest.setBirthRegistrationApplications(Collections.singletonList(existingApplication));
       ;

        // Enrich application upon update
        enrichmentUtil.enrichBirthApplicationUponUpdate(birthRegistrationRequest);

        workflowService.updateWorkflowStatus(birthRegistrationRequest);

        // Just like create request, update request will be handled asynchronously by the persister
        producer.push("update-bt-application", birthRegistrationRequest.getBirthRegistrationApplications().get(0));

        return birthRegistrationRequest.getBirthRegistrationApplications().get(0);
    }
}

...

Code Block
@Component
public class BirthApplicationValidator {

    @Autowired
    private BirthRegistrationRepository repository;

    public void validateBirthApplication(BirthRegistrationRequest birthRegistrationRequest) {
        birthRegistrationRequest.getBirthRegistrationApplications().forEach(application -> {
            if(ObjectUtils.isEmpty(application.getTenantId()))
                throw new CustomException("EG_VTBT_APP_ERR", "tenantId is mandatory for creating birth registration applications");
        });
    }

    public BirthRegistrationApplication validateApplicationExistence(BirthRegistrationApplication birthRegistrationApplication) {
        return repository.getApplications(BirthApplicationSearchCriteria.builder().applicationNumber(birthRegistrationApplication.getApplicationNumber()).build()).get(0);
    }
}

...

Code Block
@Component
public class BirthApplicationEnrichment {

    @Autowired
    private IdgenUtil idgenUtil;

    public void enrichBirthApplication(BirthRegistrationRequest birthRegistrationRequest) {
        List<String> birthRegistrationIdList = idgenUtil.getIdList(birthRegistrationRequest.getRequestInfo(), birthRegistrationRequest.getBirthRegistrationApplications().get(0).getTenantId(), "btr.registrationid", "", birthRegistrationRequest.getBirthRegistrationApplications().size());
        Integer index = 0;
        for(BirthRegistrationApplication application : birthRegistrationRequest.getBirthRegistrationApplications()){
            // Enrich audit details
            AuditDetails auditDetails = AuditDetails.builder().createdBy(birthRegistrationRequest.getRequestInfo().getUserInfo().getUuid()).createdTime(System.currentTimeMillis()).lastModifiedBy(birthRegistrationRequest.getRequestInfo().getUserInfo().getUuid()).lastModifiedTime(System.currentTimeMillis()).build();
            application.setAuditDetails(auditDetails);

            // Enrich UUID
            application.setId(UUID.randomUUID().toString());
          

             // Enrich registration Id
            application.getAddress().setRegistrationId(application.getId());

                        // Enrich address UUID
            application.getAddress().setId(UUID.randomUUID().toString());

            //Enrich application number from IDgen
            application.setApplicationNumber(birthRegistrationIdList.get(index++));

        }
    }

    public void enrichBirthApplicationUponUpdate(BirthRegistrationRequest birthRegistrationRequest) {
        // Enrich lastModifiedTime and lastModifiedBy in case of update
        birthRegistrationRequest.getBirthRegistrationApplications().get(0).getAuditDetails().setLastModifiedTime(System.currentTimeMillis());
        birthRegistrationRequest.getBirthRegistrationApplications().get(0).getAuditDetails().setLastModifiedBy(birthRegistrationRequest.getRequestInfo().getUserInfo().getUuid());
    }
} 

*** NOTE: For the sake of simplicity the above mentioned enrichment methods have been implemented. Required enrichment will vary on case by case basis.

...

iv) Once verified, we can call mdms service from within our application and fetch the required master data. For this, create a java class by the name of MdmsUtil under utils folder. Annotate this class with @Component and put the following content in the class -

Code Block
@Component
public class MdmsUtil {
    @Autowired
    private RestTemplate restTemplate;

    @Value("${egov.mdms.host}")
    private String mdmsHost;

    @Value("${egov.mdms.search.endpoint}")
    private String mdmsUrl;


    public Integer fetchRegistrationChargesFromMdms(RequestInfo requestInfo, String tenantId) {
        StringBuilder uri = new StringBuilder();
        uri.append(mdmsHost).append(mdmsUrl);
        MdmsCriteriaReq mdmsCriteriaReq = getMdmsRequestForCategoryList(requestInfo, tenantId);
        Object response = new HashMap<>();
        Integer rate = 0;
        try {
            response = restTemplate.postForObject(uri.toString(), mdmsCriteriaReq, Map.class);
            rate = JsonPath.read(response, "$.MdmsRes.BTR.RegistrationCharges.[0].amount");
        }catch(Exception e) {
      log.error("Exception occurred while fetching category lists fromreturn mdms: ",e); null;
        }
        return rate;
    }

    private MdmsCriteriaReq getMdmsRequestForCategoryList(RequestInfo requestInfo, String tenantId) {
        MasterDetail masterDetail = new MasterDetail();
        masterDetail.setName("RegistrationCharges");
        List<MasterDetail> masterDetailList = new ArrayList<>();
        masterDetailList.add(masterDetail);

        ModuleDetail moduleDetail = new ModuleDetail();
        moduleDetail.setMasterDetails(masterDetailList);
        moduleDetail.setModuleName("BTR");
        List<ModuleDetail> moduleDetailList = new ArrayList<>();
        moduleDetailList.add(moduleDetail);

        MdmsCriteria mdmsCriteria = new MdmsCriteria();
        mdmsCriteria.setTenantId(tenantId.split("\\.")[0]);]);
        mdmsCriteria.setModuleDetails(moduleDetailList);

        MdmsCriteriaReq mdmsCriteriaReq = new MdmsCriteriaReq();
        mdmsCriteriaReq.setMdmsCriteria(mdmsCriteria);
        mdmsCriteriaReq.setRequestInfo(requestInfo);

        return mdmsCriteriaReq;
    }
}

v) Add the following properties in application.properties file -

...

ii) For this tutorial, the following Id format has been added as part of this PR - Tutorial Id Format

iii) Now, restart IDGen service and Mdms service and port-forward IDGen service to port 8285 using -

...

Code Block
@Component
public class IdgenUtil {
  

     @Value("${egov.idgen.host}")
    private String idGenHost;

    @Value("${egov.idgen.path}")
    private String idGenPath;

    @Autowired
    private ObjectMapper mapper;

    @Autowired
    private ServiceRequestRepository restRepo;

    public List<String> getIdList(RequestInfo requestInfo, String tenantId, String idName, String idformat, Integer count) {
        List<IdRequest> reqList = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            reqList.add(IdRequest.builder().idName(idName).format(idformat).tenantId(tenantId).build());
        }

        IdGenerationRequest request = IdGenerationRequest.builder().idRequests(reqList).requestInfo(requestInfo).build();
        StringBuilder uri = new StringBuilder(idGenHost).append(idGenPath);
        IdGenerationResponse response = mapper.convertValue(restRepo.fetchResult(uri, request), IdGenerationResponse.class);

        List<IdResponse> idResponses = response.getIdResponses();

        if (CollectionUtils.isEmpty(idResponses))
            throw new CustomException("IDGEN ERROR", "No ids returned from idgen Service");

        return idResponses.stream().map(IdResponse::getId).collect(Collectors.toList());
    }
}

Add the following model POJOs under models folder -

Code Block
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class IdGenerationRequest {

    @JsonProperty("RequestInfo")
    private RequestInfo requestInfo;

    private List<IdRequest> idRequests;

}
Code Block
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class IdRequest {

    @JsonProperty("idName")
    @NotNull
    private String idName;

    @NotNull
    @JsonProperty("tenantId")
    private String tenantId;

    @JsonProperty("format")
    private String format;

}
Code Block
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class IdResponse {

    private String id;

}
));
    }
}

Add the following model POJOs under models folder -

Code Block
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class IdGenerationResponseIdGenerationRequest {

    @JsonProperty("RequestInfo")
    private ResponseInfoRequestInfo responseInforequestInfo;

    private List<IdResponse>List<IdRequest> idResponsesidRequests;

}

...

Code Block
#Idgen Config
egov.idgen.host=http://localhost:8285/
egov.idgen.path=egov-idgen/id/_generate

c. Integration with User service - Integration with user service requires the following steps -

i) Create a new class under utils by the name of UserUtil and update the User POJO to have the following content -

Code Block
@Getter
@Setter
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class User  IdRequest {

       @JsonProperty("ididName")
    @NotNull
    private LongString ididName;
=
null;    @NotNull
     @JsonProperty("uuidtenantId")
   
    private String uuid = nulltenantId;


       @JsonProperty("userNameformat")
    private String format;

}
Code Block
@Data
@AllArgsConstructor
private@NoArgsConstructor
String@Builder
userNamepublic =class null;IdResponse {

    private String  @JsonProperty("password")
        private String password = null;

 id;

}
Code Block
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class IdGenerationResponse {

    private ResponseInfo responseInfo;

     @JsonProperty("salutation")
private List<IdResponse> idResponses;

}

vi) Add the following properties in application.properties file -

Code Block
#Idgen Config
egov.idgen.host=http://localhost:8285/
egov.idgen.path=egov-idgen/id/_generate
 private String salutation = null;

   

c. Integration with User service - Integration with user service requires the following steps -

i) Create a new class under utils by the name of UserUtil and update the User POJO to have the following content -

Code Block
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class User   {
    @JsonProperty("genderid")
        private StringLong genderid = null;

        @JsonProperty("mobileNumberuuid")

       private String mobileNumberuuid = null;

  

    @JsonProperty("emailIduserName")
   
    private String emailIduserName = null;



      @JsonProperty("altContactNumberpassword")
   
    private String altContactNumberpassword = null;
        
  

    @JsonProperty("babyFirstNamesalutation")
 
      private String babyFirstNamesalutation = null;
        
 

     @JsonProperty("babyLastNamename")

       private String babyLastNamename = null;
     

          @JsonProperty("doctorAttendingBirthgender")
   
    private String doctorAttendingBirthgender = null;
        
  

    @JsonProperty("permanentAddressmobileNumber")
 
      private String permanentAddressmobileNumber = null;

  

    @JsonProperty("permanentCityemailId")
   
    private String permanentCityemailId = null;

  

    @JsonProperty("permanentPincodealtContactNumber")

       private String permanentPincodealtContactNumber = null;

 

     @JsonProperty("correspondenceCitypan")

       private String correspondenceCitypan = null;

  

    @JsonProperty("correspondencePincodeaadhaarNumber")
        private String correspondencePincodeaadhaarNumber = null;

  

    @JsonProperty("correspondenceAddresspermanentAddress")
  
     private String correspondenceAddresspermanentAddress = null;

    @JsonProperty("permanentCity")
    private String permanentCity = null;

    @JsonProperty("hospitalNamepermanentPincode")
   
    private String hospitalNamepermanentPincode = null;


    @JsonProperty("roles")
    @Valid
    private List<Role> roles = null;@JsonProperty("placeOfBirthcorrespondenceCity")

       private String placeOfBirthcorrespondenceCity = null;

  

    @JsonProperty("activecorrespondencePincode")
   
    private BooleanString activecorrespondencePincode = null;

        @JsonProperty("localecorrespondenceAddress")
   
    private String localecorrespondenceAddress = null;

        @JsonProperty("typeactive")
   
    private StringBoolean typeactive = null;

        @JsonProperty("signaturelocale")
   
    private String signaturelocale = null;

 

     @JsonProperty("accountLockedtype")

       private BooleanString accountLockedtype = null;

  

    @JsonProperty("rolessignature")
    private String signature = null;

@Valid    @JsonProperty("accountLocked")
    private List<Role>Boolean rolesaccountLocked = null;

  

    @JsonProperty("fatherOrHusbandName")
   
    private String fatherOrHusbandName = null;


       @JsonProperty("bloodGroup")

       private String bloodGroup = null;

  

    @JsonProperty("identificationMark")
   
    private String identificationMark = null;

        @JsonProperty("photo")
   
    private String photo = null;

  

    @JsonProperty("createdBy")
  
     private Long createdBy = null;

        @JsonProperty("lastModifiedBy")
   )
    private Long lastModifiedBy = null;

  

    @JsonProperty("otpReference")
   
    private String otpReference = null;

  

    @JsonProperty("tenantId")
        private String tenantId = null;


 


    public User addRolesItem(Role rolesItem) {
            if (this.roles == null) {
            this.roles = new ArrayList<>();
            }
        this.roles.add(rolesItem);
        return this;
        }

}

ii) Annotate the created UserUtil class with @Component and add the following code in the created class -

Code Block
@Component
public class UserUtil {


    private ObjectMapper mapper;

    private ServiceRequestRepository serviceRequestRepository;

    private BTRConfiguration config;

    @Autowired
    public UserUtil(ObjectMapper mapper, ServiceRequestRepository serviceRequestRepository, BTRConfiguration config) {
        this.mapper = mapper;
        this.serviceRequestRepository = serviceRequestRepository;
        this.config = config;
    }

    /**
     * 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(config.getUserSearchEndpoint())  || uri.toString().contains(config.getUserUpdateEndpoint()))
            dobFormat="yyyy-MM-dd";
        else if(uri.toString().contains(config.getUserCreateEndpoint()))
            dobFormat = "dd/MM/yyyy";
        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("IllegalArgumentException","ObjectMapper not able to convertValue in userCall");
        }
    }


    /**
     * Parses date formats to long for all users in responseMap
     * @param responeMap LinkedHashMap got from user api response
     */

    public void parseResponse(LinkedHashMap responeMap, String dobFormat){
        List<LinkedHashMap> users = (List<LinkedHashMap>)responeMap.get("user");
        String format1 = "dd-MM-yyyy HH:mm:ss";
        if(users!=null){
            users.forEach( map -> {
                        map.put("createdDate",dateTolong((String)map.get("createdDate"),format1));
                        if((String)map.get("lastModifiedDate")!=null)
                            map.put("lastModifiedDate",dateTolong((String)map.get("lastModifiedDate"),format1));
                        if((String)map.get("dob")!=null)
                            map.put("dob",dateTolong((String)map.get("dob"),dobFormat));
                        if((String)map.get("pwdExpiryDate")!=null)
                            map.put("pwdExpiryDate",dateTolong((String)map.get("pwdExpiryDate"),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","Failed to parse date format in user");
        }
        return  d.getTime();
    }

    /**
     * enriches the userInfo with statelevel tenantId and other fields
     * @param mobileNumber
     * @param tenantId
     * @param userInfo
     */
    public void addUserDefaultFields(String mobileNumber,String tenantId, User userInfo){
        Role role = getCitizenRole(tenantId);
        userInfo.setRoles(Collections.singletonList(role));
        userInfo.setType("CITIZEN");
        userInfo.setUserName(mobileNumber);
        userInfo.setTenantId(getStateLevelTenant(tenantId));
        userInfo.setActive(true);
    }

    /**
     * Returns role object for citizen
     * @param tenantId
     * @return
     */
    private Role getCitizenRole(String tenantId){
        Role role = new Role();
        role.setCode("CITIZEN");
        role.setName("Citizen");
        role.setTenantId(getStateLevelTenant(tenantId));
        return role;
    }

    public String getStateLevelTenant(String tenantId){
        return tenantId.split("\\.")[0];
    }

}

iii) Create the following POJOs -

Code Block
@AllArgsConstructor
@Getter
@NoArgsConstructor
public class CreateUserRequest {

    @JsonProperty("requestInfo")
    private RequestInfo requestInfo;

    @JsonProperty("user")
    private User user;

}
Code Block
@Getter
@Setter
public class UserSearchRequest {

    @JsonProperty("RequestInfo")
    private RequestInfo requestInfo;

    @JsonProperty("uuid")
    private List<String> uuid;

    @JsonProperty("id")
    private List<String> id;

    @JsonProperty("userName")
    private String userName;

    @JsonProperty("name")
    private String name;

    @JsonProperty("babyFirstNamemobileNumber")
    private String babyFirstName;
       mobileNumber;

    @JsonProperty("babyLastNameaadhaarNumber")
    private String babyLastNameaadhaarNumber;

       
    @JsonProperty("doctorAttendingBirthpan")
    private String doctorAttendingBirth;

    @JsonProperty("mobileNumber")
    private String mobileNumberpan;

    @JsonProperty("emailId")
    private String emailId;

    @JsonProperty("fuzzyLogic")
    private boolean fuzzyLogic;
   

    @JsonProperty("hospitalName")
    private String hospitalName;
        
    @JsonProperty("placeOfBirth")
    private String placeOfBirth;

    @JsonProperty("active")
    @Setter
    private Boolean active;

    @JsonProperty("tenantId")
    private String tenantId;

    @JsonProperty("pageSize")
    private int pageSize;

    @JsonProperty("pageNumber")
    private int pageNumber = 0;

    @JsonProperty("sort")
    private List<String> sort = Collections.singletonList("name");

    @JsonProperty("userType")
    private String userType;

    @JsonProperty("roleCodes")
    private List<String> roleCodes;

}
Code Block
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class UserDetailResponse {
    @JsonProperty("responseInfo")
    ResponseInfo responseInfo;

    @JsonProperty("user")
    List<User> user;
}

v) Create a class by the name of UserService under service folder and add the following content to it -

Code Block
@Service
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.getApplicant().getId()))
                enrichUser(application, request.getRequestInfo());
            else
                upsertUser(application, request.getRequestInfo());
        });
    }

    private void upsertUser(BirthRegistrationApplication application, RequestInfo requestInfo){
        //Applicant applicant = application.getApplicant();
        User user = User.builder().userName(applicantapplication.getUserName())
                   .password(application.getPassword())
                .passwordname(applicantapplication.getPasswordgetBabyFirstName())
                   .gender(application.getGender())
                .salutationmobileNumber(applicantapplication.getSalutationgetApplicantMobileNumber())
                 .emailId(application.getEmailId())
                  .namealtContactNumber(applicantapplication.getNamegetAltContactNumber())
                  .permanentAddress(application.getPermanentAddress())
                 .genderpermanentCity(applicantapplication.getGendergetPermanentCity())
                   .permanentPincode(application.getPermanentPincode())
                .mobileNumbercorrespondenceCity(applicantapplication.getMobileNumbergetCorrespondenceCity())
                .correspondencePincode(application.getCorrespondencePincode())
                   .emailIdcorrespondenceAddress(applicantapplication.getEmailIdgetCorrespondenceAddress())
                   .active(application.getActive())
                .altContactNumberlocale(applicantapplication.getAltContactNumbergetLocale())
                  .signature(application.getSignature())
                 .babyFirstNameaccountLocked(applicantapplication.babyFirstNamegetAccountLocked())
                .fatherOrHusbandName(application.getFatherOrHusbandName())
                   .babyLastNamebloodGroup(applicantapplication.babyLastNamegetBloodGroup())
                .identificationMark(application.getIdentificationMark())
                   .doctorAttendingBirthphoto(applicantapplication.doctorAttendingBirthgetPhoto())
                   .otpReference(application.getOtpReference())
                .hospitalNametenantId(applicantapplication.hospitalNamegetTenantId())
                   .type(application.getType())
                .placeOfBirthtenantId(applicantapplication.placeOfBirthgetTenantId())
                .build();
        String tenantId   = application.getTenantId();
      .permanentAddress(applicant.getPermanentAddress())     User userServiceResponse = null;

        // Search on mobile number as user name
        UserDetailResponse userDetailResponse = searchUser(userUtils.permanentCity(applicant.getPermanentCitygetStateLevelTenant(tenantId),null, user.getMobileNumber());
        if (!userDetailResponse.getUser().isEmpty()) {
            User userFromSearch = userDetailResponse.getUser().get(0);
            .permanentPincode(applicant.getPermanentPincode())if(!user.getName().equalsIgnoreCase(userFromSearch.getName())){
                userServiceResponse = updateUser(requestInfo,user,userFromSearch);
            }
     .correspondenceCity(applicant.getCorrespondenceCity())       else userServiceResponse = userDetailResponse.getUser().get(0);
        }
        else {
       .correspondencePincode(applicant.getCorrespondencePincode())     userServiceResponse = createUser(requestInfo,tenantId,user);
        }

        // Enrich the accountId
        application.correspondenceAddresssetId(applicantuserServiceResponse.getCorrespondenceAddressgetUuid());
    }


    private void enrichUser(BirthRegistrationApplication application, RequestInfo requestInfo){

        String applicantMobileNumber         .active(applicant.getActive())= application.getApplicantMobileNumber();
        String tenantId = application.getTenantId();

        UserDetailResponse userDetailResponse = searchUser(userUtils.getStateLevelTenant(tenantId),null,applicantMobileNumber);

          .locale(applicant.getLocaleif(userDetailResponse.getUser().isEmpty())
            throw new CustomException("INVALID_ACCOUNTID","No user exist for the given accountId");

              .signature(applicant.getSignatureelse application.setId(userDetailResponse.getUser().get(0).getUuid());

    }

    /**
     * Creates the user from the given userInfo by calling user service
     *  .accountLocked(applicant.getAccountLocked())@param requestInfo
     * @param tenantId
     * @param userInfo
     * @return
             .fatherOrHusbandName(applicant.getFatherOrHusbandName())*/
    private User createUser(RequestInfo requestInfo,String tenantId, User userInfo) {

        userUtils.addUserDefaultFields(userInfo.getMobileNumber(),tenantId, userInfo);
        StringBuilder uri = new  .bloodGroupStringBuilder(applicantconfig.getBloodGroupgetUserHost())
                   .append(config.getUserContextPath())
                .identificationMarkappend(applicantconfig.getIdentificationMarkgetUserCreateEndpoint());


        UserDetailResponse userDetailResponse = userUtils.userCall(new CreateUserRequest(requestInfo, userInfo), uri);

                  .photo(applicant.getPhoto())return userDetailResponse.getUser().get(0);

    }

    /**
     * Updates the given user by calling user service
     * @param requestInfo
  .otpReference(applicant.getOtpReference())   * @param user
     * @param userFromSearch
     * @return
     */
    private User updateUser(RequestInfo requestInfo,User   .tenantId(applicant.getTenantId()user,User userFromSearch) {

        userFromSearch.setName(user.getName());
        userFromSearch.setActive(true);

        StringBuilder uri = new    .type(applicant.getTypeStringBuilder(config.getUserHost())
                   .append(config.getUserContextPath())
                .rolesappend(applicantconfig.getRolesgetUserUpdateEndpoint());


        UserDetailResponse userDetailResponse = userUtils.userCall(new CreateUserRequest(requestInfo, userFromSearch), uri);

                  .tenantId(applicant.getTenantId())return userDetailResponse.getUser().get(0);

    }

    /**
     * calls the user search API based on the given accountId and userName
      .aadhaarNumber(applicant.getAadhaarNumber())
* @param stateLevelTenant
     * @param accountId
     * @param userName
     * @return
     */
    private UserDetailResponse .buildsearchUser();String stateLevelTenant, String       accountId, String tenantIduserName){
=
applicant.getTenantId();        UserSearchRequest User userServiceResponseuserSearchRequest =new nullUserSearchRequest();
         // Search on mobile number as user name
 userSearchRequest.setActive(true);
        userSearchRequest.setUserType("CITIZEN");
      UserDetailResponse userDetailResponse = searchUser(userUtils.getStateLevelTenant(tenantId),null, user.getMobileNumber());userSearchRequest.setTenantId(stateLevelTenant);

        if (!userDetailResponseStringUtils.getUserisEmpty(accountId) && StringUtils.isEmpty(userName))
{             User userFromSearch = userDetailResponse.getUser().get(0)return null;

           if(!userStringUtils.getNameisEmpty(accountId).equalsIgnoreCase(userFromSearch.getName())){)
            userSearchRequest.setUuid(Collections.singletonList(accountId));

   userServiceResponse = updateUser(requestInfo,user,userFromSearch);   if(!StringUtils.isEmpty(userName))
         }   userSearchRequest.setUserName(userName);

        elseStringBuilder userServiceResponseuri = new userDetailResponseStringBuilder(config.getUsergetUserHost()).get(0append(config.getUserSearchEndpoint());
        }
return userUtils.userCall(userSearchRequest,uri);

    }

else {   /**
     * calls   userServiceResponse = createUser(requestInfo,tenantId,user);
        }
the user search API based on the given list of user uuids
     * @param uuids
// Enrich the accountId  * @return
     applicant.setId(userServiceResponse.getUuid());
    }

*/
    private voidMap<String,User> enrichUsersearchBulkUser(BirthRegistrationApplication application, RequestInfo requestInfoList<String> uuids){

        StringUserSearchRequest accountIduserSearchRequest =new application.getApplicantUserSearchRequest().getId();
        String tenantId = application.getApplicant().getTenantId()userSearchRequest.setActive(true);
         UserDetailResponse userDetailResponse = searchUser(userUtils.getStateLevelTenant(tenantId),accountId,null);userSearchRequest.setUserType("CITIZEN");


        if(userDetailResponse.getUser().!CollectionUtils.isEmpty(uuids))
            throw new CustomException("INVALID_ACCOUNTID","No user exist for the given accountId");userSearchRequest.setUuid(uuids);


        StringBuilder uri =   else application.getApplicant().setId(userDetailResponse.getUser().get(0).getUuidnew StringBuilder(config.getUserHost()).append(config.getUserSearchEndpoint());
     }   UserDetailResponse userDetailResponse  /**= userUtils.userCall(userSearchRequest,uri);
     * Creates the userList<User> from the given userInfo by calling user serviceusers = userDetailResponse.getUser();

    * @param requestInfo  if(CollectionUtils.isEmpty(users))
   * @param tenantId      * @paramthrow userInfo
     * @returnnew CustomException("USER_NOT_FOUND","No user found for the uuids");

     */   Map<String,User> idToUserMap private User createUser(RequestInfo requestInfo,String tenantId, User userInfo) {= users.stream().collect(Collectors.toMap(User::getUuid, Function.identity()));

        return idToUserMap;
   userUtils.addUserDefaultFields(userInfo.getMobileNumber(),tenantId, userInfo);
        StringBuilder uri = new StringBuilder(config.getUserHost())
 }

}

vi) Add the following properties in application.properties file -

Code Block
#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

d) Integration with URL Shortener - Integration with URL shortener requires the following steps -

i) Create a new class by the name of UrlShortnerUtil

ii) Annotate this class with @Component and add the following code -

Code Block
@Slf4j
@Component
public class UrlShortnerUtil {
    @Autowired
    private RestTemplate restTemplate;

  .append(config.getUserContextPath())  @Autowired
    private BTRConfiguration config;

    public String .append(config.getUserCreateEndpoint());getShortenedUrl(String url){

         UserDetailResponse userDetailResponseHashMap<String,String> body = userUtils.userCall(new CreateUserRequest(requestInfo, userInfo), uriHashMap<>();
         return userDetailResponse.getUser().get(0)body.put("url",url);
     }   StringBuilder builder = /**
     * Updates the given user by calling user service
     * @param requestInfo
     * @param user
     * @param userFromSearch
     * @return
     */
    private User updateUser(RequestInfo requestInfo,User user,User userFromSearch) {new StringBuilder(config.getUrlShortnerHost());
        builder.append(config.getUrlShortnerEndpoint());
        String res =   userFromSearchrestTemplate.setNamepostForObject(userbuilder.getNametoString());,         userFromSearch.setActive(truebody, String.class);

        StringBuilder uri = new StringBuilder(config.getUserHost())if(StringUtils.isEmpty(res)){
            log.error("URL_SHORTENING_ERROR", "Unable to  .append(config.getUserContextPath())shorten url: " + url);
            return url;
  .append(config.getUserUpdateEndpoint());      }
    UserDetailResponse userDetailResponse = userUtils.userCall(new CreateUserRequest(requestInfo, userFromSearch), uri) else return res;
    }
}

iii) Add the following properties in application.properties file -

Code Block
#url shortner
 return userDetailResponse.getUser().get(0);

    }

    /**
     * calls the user search API based on the given accountId and userName
     * @param stateLevelTenant
     * @param accountId
     * @param userNameegov.url.shortner.host=https://dev.digit.org
egov.url.shortner.endpoint=/egov-url-shortening/shortener

e) Integration with workflow - Integration with workflow service requires the following steps -

i) Update the BirthRegistrationApplication POJO to have following contents -

Code Block
@ApiModel(description = "A Object holds the basic data for a Birth Registration Application")
@Validated
@javax.annotation.Generated(value = "org.egov.codegen.SpringBootCodegen", date = "2022-07-26T12:39:05.988+05:30")

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class BirthRegistrationApplication   {
        @JsonProperty("id")
        private String id = null;

     * @return  @JsonProperty("tenantId")
   */     private UserDetailResponse searchUser(String stateLevelTenant,tenantId String= accountId,null;
String
userName){        @JsonProperty("applicationNumber")
 UserSearchRequest userSearchRequest =new UserSearchRequest();    private String applicationNumber =  userSearchRequest.setActive(true);null;

        userSearchRequest.setUserType@JsonProperty("CITIZENbabyFirstName");
        private  userSearchRequest.setTenantId(stateLevelTenant);

String babyFirstName = null;

      if(StringUtils.isEmpty(accountId) && StringUtils.isEmpty(userName))@JsonProperty("babyLastName")
        private String babyLastName  return= null;

        if(!StringUtils.isEmpty(accountId)@JsonProperty("fatherName")
        private String fatherName  userSearchRequest.setUuid(Collections.singletonList(accountId))= null;

        if(!StringUtils.isEmpty(userName))
  @JsonProperty("motherName")
        private String motherName = null;

        userSearchRequest.setUserName(userName);@JsonProperty("doctorName")
        private StringBuilderString uridoctorName = new StringBuilder(config.getUserHost()).append(config.getUserSearchEndpoint())null;

       return userUtils.userCall(userSearchRequest,uri);@JsonProperty("hospitalName")
     }   private String hospitalName /**
     * calls the user search API based on the given list of user uuids= null;

        @JsonProperty("placeOfBirth")
        private *String @paramplaceOfBirth uuids= null;

   * @return    @JsonProperty("timeOfBirth")
 */     private Map<String,User> searchBulkUser(List<String> uuids){

  private Integer timeOfBirth = null;

     UserSearchRequest userSearchRequest =new UserSearchRequest@JsonProperty("address");
        private  userSearchRequest.setActive(true);Address address = null;

        userSearchRequest.setUserType@JsonProperty("CITIZENauditDetails");
        private  if(!CollectionUtils.isEmpty(uuids))
 AuditDetails auditDetails = null;

         userSearchRequest.setUuid(uuids);@JsonProperty("applicantMobileNumber")
        private String StringBuilderapplicantMobileNumber uri = new StringBuilder(config.getUserHost()).append(config.getUserSearchEndpoint());
null;

      UserDetailResponse userDetailResponse = userUtils.userCall(userSearchRequest,uri);@JsonProperty("userName")
        private List<User>String usersuserName = userDetailResponse.getUser()null;

        if(CollectionUtils.isEmpty(users))@JsonProperty("password")
        private String password = null;

 throw new CustomException("USER_NOT_FOUND","No user found for the uuids@JsonProperty("gender");
        private Map<String,User>String idToUserMapgender = users.stream().collect(Collectors.toMap(User::getUuid, Function.identity())) null;

        return idToUserMap;@JsonProperty("emailId")
     }  }

vi) Add the following properties in application.properties file -

Code Block
#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

d) Integration with URL Shortener - Integration with URL shortener requires the following steps -

i) Create a new class by the name of UrlShortnerUtil

ii) Annotate this class with @Component and add the following code -

Code Block
@Autowired
private RestTemplate restTemplate;

@Autowired
private BTRConfiguration config;

public String getShortenedUrl(String url){

  HashMap<String,String> body = new HashMap<>();
  body.put("url",url);
  StringBuilder builder = new StringBuilder(config.getUrlShortnerHost());
  builder.append(config.getUrlShortnerEndpoint());
  String res = restTemplate.postForObject(builder.toString(), body, String.class);

  if(StringUtils.isEmpty(res)){
    log.error("URL_SHORTENING_ERROR", "Unable to shorten url: " + url); ;
    return url;
  }
  else return res;
}

iii) Add the following properties in application.properties file -

Code Block
#url shortner
egov.url.shortner.host=https://dev.digit.org
egov.url.shortner.endpoint=/egov-url-shortening/shortener

e) Integration with workflow - Integration with workflow service requires the following steps -

i) Add a workflow object to BirthRegistrationApplication POJO -

Code Block
@Valid
@JsonProperty("workflow")
private Workflow workflow = null; 

Create ProcessInstance, State, Action, ProcessInstanceRequest, ProcessInstanceResponse , BusinessService , BusinessServiceResponse POJOs -

Code Block
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
@EqualsAndHashCode(of = { "id" })
@ToString
public class ProcessInstance {

    @Size(max = 64) private String emailId = null;

        @JsonProperty("altContactNumber")
        private String altContactNumber = null;

        @JsonProperty("permanentAddress")
        private String permanentAddress = null;

        @JsonProperty("permanentCity")
        private String permanentCity = null;

        @JsonProperty("permanentPincode")
        private String permanentPincode = null;

        @JsonProperty("correspondenceCity")
        private String correspondenceCity = null;

        @JsonProperty("idcorrespondencePincode")
    private String id;    private String @NotNullcorrespondencePincode = null;

 @Size(max = 128)     @JsonProperty("tenantIdcorrespondenceAddress")
    private String tenantId;  private String correspondenceAddress = @NotNull
 null;

 @Size(max = 128)     @JsonProperty("businessServiceactive")
    private String businessService;  private Boolean active = @NotNullnull;

   @Size(max = 128)
    @JsonProperty("businessIdlocale")
    private String businessId;  private String locale = @NotNull
null;

  @Size(max = 128)     @JsonProperty("actiontype")
    private String action;  private String type = @NotNull
null;

  @Size(max = 64)     @JsonProperty("moduleNamesignature")
        private String signature = moduleNamenull;

        @JsonProperty("stateaccountLocked")
        private Boolean accountLocked State= statenull;

        @JsonProperty("commentfatherOrHusbandName")
        private String fatherOrHusbandName = commentnull;

        @JsonProperty("documentsbloodGroup")
     @Valid   private String privatebloodGroup List<Document>= documentsnull;

        @JsonProperty("assignesidentificationMark")
    private List<User> assignes;  private String identificationMark = publicnull;
ProcessInstance
addDocumentsItem(Document documentsItem) {      @JsonProperty("photo")
  if (this.documents == null) {  private String photo = null;

     this.documents = new ArrayList<>@JsonProperty("otpReference");
        }private String otpReference = null;

   if (!this.documents.contains(documentsItem))    @Valid
        this.documents.add(documentsItem);@JsonProperty("workflow")
        private returnWorkflow this;workflow =    }

} 
null;

}

Create ProcessInstance, State, Action, ProcessInstanceRequest, ProcessInstanceResponse , BusinessService , BusinessServiceResponse POJOs -

Code Block
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
@ToString
@EqualsAndHashCode(of = { "tenantId","businessServiceId","state"id" })
@ToString
public class StateProcessInstance   {

  

    @Size(max =256 64)

       @JsonProperty("uuidid")
   
    private String uuidid;

    @NotNull
    @Size(max =256 128)
        @JsonProperty("tenantId")
   
    private String tenantId;

    @NotNull
    @Size(max =256)
    128)
    @JsonProperty("businessServiceIdbusinessService")
   
    private String businessServiceIdbusinessService;

        @JsonProperty("sla")
        private Long sla;

@NotNull
       @Size(max =256 128)
        @JsonProperty("statebusinessId")
 
      private String statebusinessId;

    @NotNull
    @Size(max =256)
    128)
    @JsonProperty("applicationStatusaction")
        private String applicationStatusaction;

    @NotNull
    @JsonProperty("docUploadRequired")
        private Boolean docUploadRequired;@Size(max = 64)
          @JsonProperty("isStartStatemoduleName")
   
    private BooleanString isStartStatemoduleName;



      @JsonProperty("isTerminateStatestate")
   
    private BooleanState isTerminateStatestate;

        @JsonProperty("isStateUpdatablecomment")
   
    private BooleanString isStateUpdatablecomment;

        @JsonProperty("actionsdocuments")
   
    @Valid
    private List<Document> documents;

private List<Action> actions;     @JsonProperty("assignes")
    private AuditDetailsList<User> auditDetailsassignes;


  

    public StateProcessInstance addActionsItemaddDocumentsItem(ActionDocument actionsItemdocumentsItem) {
 
          if (this.actionsdocuments == null) {
            this.actionsdocuments = new ArrayList<>();
        }
   }     if (!this.documents.contains(documentsItem))
            this.actionsdocuments.add(actionsItemdocumentsItem);

        return this;
        }

}
Code Block
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
@ToString
@EqualsAndHashCode(of = {"tenantId","currentStatebusinessServiceId","actionstate"})
public class ActionState   {

        @Size(max=256)
   
    @JsonProperty("uuid")
   
    private String uuid;



      @Size(max=256)
 
      @JsonProperty("tenantId")
   
    private String tenantId;

  

    @Size(max=256)

       @JsonProperty("currentStatebusinessServiceId")
    private String businessServiceId;

    private String currentState;@JsonProperty("sla")
    private Long sla;

    @Size(max=256)
 
      @JsonProperty("actionstate")
   
    private String actionstate;

    @Size(max=256)
   @Size(max=256) @JsonProperty("applicationStatus")
    private String applicationStatus;

    @JsonProperty("nextStatedocUploadRequired")
    private Boolean docUploadRequired;

    @JsonProperty("isStartState")
    private StringBoolean nextStateisStartState;

    @JsonProperty("isTerminateState")
   @Size(max=1024) private Boolean isTerminateState;

    @JsonProperty("rolesisStateUpdatable")
    private Boolean isStateUpdatable;

@Valid    @JsonProperty("actions")
    @Valid
private List<String> roles;  private List<Action> actions;

    private AuditDetails auditDetails;


 


    public ActionState addRolesItemaddActionsItem(StringAction rolesItemactionsItem) {
   
        if (this.rolesactions == null) {
            this.rolesactions = new ArrayList<>();
   
        }
        this.rolesactions.add(rolesItemactionsItem);
        return this;
  
     }

}
Code Block
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
@ToString
public@EqualsAndHashCode(of class= ProcessInstanceRequest {
    @JsonProperty("RequestInfo")
    private RequestInfo requestInfo;
{"tenantId","currentState","action"})
public class Action   {

    @Size(max=256)
    @JsonProperty("ProcessInstancesuuid")
    @Validprivate String uuid;

 @NotNull   @Size(max=256)
 private List<ProcessInstance> processInstances;  @JsonProperty("tenantId")
    publicprivate ProcessInstanceRequest addProcessInstanceItem(ProcessInstance processInstanceItem) {String tenantId;

    @Size(max=256)
    if (this.processInstances == null) {
   @JsonProperty("currentState")
    private String currentState;

    @Size(max=256)
  this.processInstances = new ArrayList<>();@JsonProperty("action")
    private String action;

}    @Size(max=256)
    this.processInstances.add(processInstanceItem);@JsonProperty("nextState")
    private String   return thisnextState;

   }

}
Code Block
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ProcessInstanceResponse { @Size(max=1024)
    @JsonProperty("ResponseInforoles")
    private@Valid
ResponseInfo responseInfo;   private   @JsonProperty("ProcessInstances")
List<String> roles;

  @Valid     private List<ProcessInstance>AuditDetails processInstancesauditDetails;


    public ProcessInstanceResponseAction addProceInstanceItemaddRolesItem(ProcessInstanceString proceInstanceItemrolesItem) {
        if (this.processInstancesroles == null) {
            this.processInstancesroles = new ArrayList<>();
        }
        this.processInstancesroles.add(proceInstanceItemrolesItem);
        return this;
    }

}
Code Block
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
@ToString
@EqualsAndHashCode(of =
{"tenantId","businessService"})
@JsonInclude(JsonInclude.Include.NON_NULL)
public class BusinessService  ProcessInstanceRequest {

    @Size(max=256)
    @JsonProperty("tenantIdRequestInfo")
    private String tenantId = nullRequestInfo requestInfo;

    @Size(max=256)
    @JsonProperty("uuidProcessInstances")
    private@Valid
String uuid = null; @NotNull
    @Size(max=256)
 private List<ProcessInstance> processInstances;


@JsonProperty("businessService")    public privateProcessInstanceRequest String businessService = null;addProcessInstanceItem(ProcessInstance processInstanceItem) {
      @Size(max=256)  if (this.processInstances  @JsonProperty("business"== null) {
   private String business = null;     this.processInstances @Size(max=1024) new    @JsonProperty("getUri")ArrayList<>();
    private String getUri = null;}
     @Size(max=1024)     @JsonProperty("postUri")
    private String postUri = nullthis.processInstances.add(processInstanceItem);
     @JsonProperty("businessServiceSla")   return this;
private Long businessServiceSla = null;}

}
Code Block
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@NotNull@Builder
public class ProcessInstanceResponse  @Valid{
    @JsonProperty("statesResponseInfo")
    private List<State> states = nullResponseInfo responseInfo;

    @JsonProperty("auditDetailsProcessInstances")
    private@Valid
   AuditDetails auditDetailsprivate =List<ProcessInstance> nullprocessInstances;



    public BusinessServiceProcessInstanceResponse addStatesItemaddProceInstanceItem(StateProcessInstance statesItemproceInstanceItem) {
        if (this.statesprocessInstances == null) {
            this.statesprocessInstances = new ArrayList<>();
        }
        this.statesprocessInstances.add(statesItemproceInstanceItem);
        return this;
    }

}
Code Block
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
/**
     * Returns the currentState with the given uuid if not present returns null
     * @param uuid the uuid of the currentState to be returned
     * @return
     */
    public State getStateFromUuid(String uuid) {
        State state @ToString
@EqualsAndHashCode(of = {"tenantId","businessService"})
@JsonInclude(JsonInclude.Include.NON_NULL)
public class BusinessService   {

    @Size(max=256)
    @JsonProperty("tenantId")
    private String tenantId = null;

       if(this.states!=null){@Size(max=256)
            for(State s : this.states){@JsonProperty("uuid")
    private String uuid = null;

      if(s.getUuid().equalsIgnoreCase(uuid)){@Size(max=256)
    @JsonProperty("businessService")
    private String businessService = null;

     state = s;@Size(max=256)
          @JsonProperty("business")
    private String business =   breaknull;

    @Size(max=1024)
    @JsonProperty("getUri")
    private }String getUri = null;

    @Size(max=1024)
   } @JsonProperty("postUri")
    private String postUri }= null;

    @JsonProperty("businessServiceSla")
 return state;  private Long businessServiceSla }= null;

 } 
Code Block
@Data @NoArgsConstructor @AllArgsConstructor
@Builder
@ToString@NotNull
public class BusinessServiceResponse { @Valid
    @JsonProperty("ResponseInfostates")
    private ResponseInfo responseInfoList<State> states = null;

    @JsonProperty("BusinessServicesauditDetails")
    @Validprivate AuditDetails auditDetails   @NotNull
    private List<BusinessService> businessServices;= null;



    public BusinessServiceResponseBusinessService addBusinessServiceItemaddStatesItem(BusinessServiceState businessServiceItemstatesItem) {
        if (this.businessServicesstates == null) {
            this.businessServicesstates = new ArrayList<>();
        }
        this.businessServicesstates.add(businessServiceItemstatesItem);
        return this;
    }


 } 

ii) Next, we have to create a class to transition the workflow object across its states. For this, create a class by the name of WorkflowService and annotate it with @Service annotation. Add the following content to this class -

Code Block
@Component public class WorkflowService {

    @Autowired
    private ObjectMapper mapper;

    @Autowired
    private ServiceRequestRepository repository;

    @Autowired
    private BTRConfiguration config; /**
     public* voidReturns updateWorkflowStatus(BirthRegistrationRequest birthRegistrationRequest) {
        birthRegistrationRequest.getBirthRegistrationApplications().forEach(application -> {
            ProcessInstance processInstance = getProcessInstanceForBTR(application, birthRegistrationRequest.getRequestInfo());
  the currentState with the given uuid if not present returns null
     * @param uuid the uuid of the currentState to be returned
     * @return
     */
   ProcessInstanceRequest workflowRequestpublic =State new ProcessInstanceRequest(birthRegistrationRequest.getRequestInfo(), Collections.singletonList(processInstance));
getStateFromUuid(String uuid) {
        State state = callWorkFlow(workflowRequest)null;
        });if(this.states!=null){
     }      public for(State s callWorkFlow(ProcessInstanceRequest workflowReq) : this.states){
         ProcessInstanceResponse response = null;         StringBuilder url = new StringBuilder(config.getWfHostif(s.getUuid().concat(config.getWfTransitionPath()));equalsIgnoreCase(uuid)){
              Object optional = repository.fetchResult(url, workflowReq);  state = s;
    response = mapper.convertValue(optional, ProcessInstanceResponse.class);         return response.getProcessInstances().get(0).getState();   break;
 }      private ProcessInstance getProcessInstanceForBTR(BirthRegistrationApplication application, RequestInfo requestInfo) {   }
     Workflow workflow = application.getWorkflow();    }
     ProcessInstance processInstance = new}
ProcessInstance();         processInstance.setBusinessId(application.getApplicationNumber())return state;
    }



processInstance.setAction(workflow.getAction());
  }
Code Block
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class BusinessServiceResponse {

    processInstance.setModuleName@JsonProperty("birth-servicesResponseInfo");
    private ResponseInfo responseInfo;

   processInstance.setTenantId(application.getTenantId()); @JsonProperty("BusinessServices")
    @Valid
   processInstance.setBusinessService("BTR");  @NotNull
    private  processInstance.setDocuments(workflow.getDocuments())List<BusinessService> businessServices;


    public BusinessServiceResponse processInstance.setComment(workflow.getComments());
addBusinessServiceItem(BusinessService businessServiceItem) {
        if (!CollectionUtils.isEmpty(workflow.getAssignes()))this.businessServices == null) {
            List<User> usersthis.businessServices = new ArrayList<>();
        }
        workflowthis.businessServices.getAssignesadd(businessServiceItem).forEach(uuid -> {;
        return this;
    }



digit.web.models.User user = new digit.web.models.User()}

ii) Next, we have to create a class to transition the workflow object across its states. For this, create a class by the name of WorkflowService and annotate it with @Service annotation. Add the following content to this class -

Code Block
@Component
public class WorkflowService {

    @Autowired
    private ObjectMapper mapper;

    @Autowired
    private ServiceRequestRepository repository;

  user.setUuid(uuid);  @Autowired
    private BTRConfiguration config;

    public void users.add(user);updateWorkflowStatus(BirthRegistrationRequest birthRegistrationRequest) {
        birthRegistrationRequest.getBirthRegistrationApplications().forEach(application -> {
 });           ProcessInstance processInstance  processInstance.setAssignes(users= getProcessInstanceForBTR(application, birthRegistrationRequest.getRequestInfo());
        }    ProcessInstanceRequest workflowRequest = new ProcessInstanceRequest(birthRegistrationRequest.getRequestInfo(), Collections.singletonList(processInstance));
   return processInstance;      }  callWorkFlow(workflowRequest);
   private BusinessService getBusinessService(BirthRegistrationApplication application, RequestInfo requestInfo});
{    }

   String tenantIdpublic =State application.getTenantId();callWorkFlow(ProcessInstanceRequest workflowReq) {

        StringBuilderProcessInstanceResponse urlresponse = getSearchURLWithParams(tenantId, "BTR")null;
        RequestInfoWrapperStringBuilder requestInfoWrapperurl = RequestInfoWrapper.buildernew StringBuilder(config.getWfHost().requestInfoconcat(requestInfo)config.buildgetWfTransitionPath()));
        Object resultoptional = repository.fetchResult(url, requestInfoWrapperworkflowReq);

       BusinessServiceResponse response = nullmapper.convertValue(optional, ProcessInstanceResponse.class);
        try {return response.getProcessInstances().get(0).getState();
    }

    private ProcessInstance response = mapper.convertValue(result, BusinessServiceResponse.class);getProcessInstanceForBTR(BirthRegistrationApplication application, RequestInfo requestInfo) {
        Workflow workflow = application.getWorkflow();

  } catch (IllegalArgumentException e) {  ProcessInstance processInstance = new ProcessInstance();
      throw new CustomException("PARSING ERROR", "Failed to parse response of workflow business service search" processInstance.setBusinessId(application.getApplicationNumber());
        processInstance.setAction(workflow.getAction());
        }processInstance.setModuleName("birth-services");
         if (CollectionUtils.isEmpty(response.getBusinessServicesprocessInstance.setTenantId(application.getTenantId()));
            throw new CustomException("BUSINESSSERVICE_NOT_FOUND", "The businessService " + "BTR" + " is not found"processInstance.setBusinessService("BTR");
        processInstance.setDocuments(workflow.getDocuments());
         return response.getBusinessServices().get(0processInstance.setComment(workflow.getComments());

   }      private StringBuilder getSearchURLWithParams(String tenantId, String businessService) {if(!CollectionUtils.isEmpty(workflow.getAssignes())){
            StringBuilderList<User> urlusers = new StringBuilderArrayList<>(config.getWfHost());

            urlworkflow.appendgetAssignes(config).getWfBusinessServiceSearchPath());
forEach(uuid -> {
       url.append("?tenantId=");         url.append(tenantId);
        url.append("&businessServices="digit.web.models.User user = new digit.web.models.User();
        url.append(businessService);         return url;user.setUuid(uuid);
        }      public ProcessInstanceRequest getProcessInstanceForBirthRegistrationPayment(BirthRegistrationRequest updateRequest) {users.add(user);
          BirthRegistrationApplication application = updateRequest.getBirthRegistrationApplications().get(0 });

         ProcessInstance process = ProcessInstanceprocessInstance.buildersetAssignes(users);
        }

      .businessService("BTR")  return processInstance;

    }

    private BusinessService .businessIdgetBusinessService(application.getApplicationNumber())
  BirthRegistrationApplication application, RequestInfo requestInfo) {
        String tenantId =   application.comment("Payment for birth registration processed")getTenantId();
        StringBuilder url =      .moduleName("birth-services")getSearchURLWithParams(tenantId, "BTR");
        RequestInfoWrapper requestInfoWrapper =      .tenantId(application.getTenantId())RequestInfoWrapper.builder().requestInfo(requestInfo).build();
        Object result =      .action("PAY")repository.fetchResult(url, requestInfoWrapper);
        BusinessServiceResponse response       .build()= null;
        try return{
ProcessInstanceRequest.builder()            response =    mapper.requestInfo(updateRequest.getRequestInfo())convertValue(result, BusinessServiceResponse.class);
        } catch (IllegalArgumentException e) {
   .processInstances(Arrays.asList(process))         throw new CustomException("PARSING ERROR", "Failed to parse response .build();of workflow business service search");
 } } 

iii) Add the following properties to application.properties file -

Code Block
#Workflow config is.workflow.enabled=true egov.workflow.host=http://localhost:8282 egov.workflow.transition.path=/egov-workflow-v2/egov-wf/process/_transition
egov.workflow.businessservice.search.path=/egov-workflow-v2/egov-wf/businessservice/_search
egov.workflow.processinstance.search.path=/egov-workflow-v2/egov-wf/process/_search
  1. Calculation - The calculation class will contain the calculation logic for given service delivery. Based on the application submitted the calculator class will calculate the tax/charges and call billing service to generate demand.

For our guide, we are going to create a sample calculation class with some dummy logic. For this, we are going to perform the following steps -

i) Create a class under service folder by the name of CalculationService

ii) Now, annotate this class with @Service annotation and add the following logic within it -

Code Block
@Service
public class CalculationService { }

        public Double calculateLaminationCharges(BirthRegistrationApplication application){if (CollectionUtils.isEmpty(response.getBusinessServices()))
        // Add calculation logic accordingthrow to business requirement
        return 10.0;
new CustomException("BUSINESSSERVICE_NOT_FOUND", "The businessService " + "BTR" + " is not found");

  }
}

Repository Layer:

Methods in the service layer, upon performing all the business logic, call methods in the Repository layer to persist or lookup data i.e. it interacts with the configured data store. For executing the queries, JdbcTemplate class is used. JdbcTemplate takes care of creation and release of resources such as creating and closing the connection etc. All database operations namely insert, update, search and delete can be performed on the database using methods of JdbcTemplate class.

On DIGIT however, we handle create and update operations asynchronously. Our persister service listens on the topic to which service applications are pushed for insertion and updation. Persister then takes care of executing insert and update operations on the database without hogging our application’s threads.

That leaves us with execution of search queries on the database to return applications as per the search parameters provided by the user.

For this guide, these are the steps that we will be taking to implement repository layer -

i) Create querybuilder and rowmapper folders within repository folder.

ii) Create a class by the name of BirthApplicationQueryBuilder in querybuilder folder and annotate it with @Component annotation. Put the following content in BirthApplicationQueryBuilder class -

Code Block
@Component
public class BirthApplicationQueryBuilder {      return response.getBusinessServices().get(0);
    }

    private StringBuilder static final String BASE_BTR_QUERY = " SELECT btr.id as bid, btr.tenantid as btenantid, btr.hospitalName as bhospitalName, btr.applicationnumber as bapplicationnumber, btr.applicantid as bapplicantid, btr.dateOfBirth as bdateOfBirth, btr.babyFirstName as bbabyFirstName, btr.babyLastName as bbabyLastName, btr.motherName as bmotherName, btr.fatherName as bfatherName, btr.doctorAttendingBirth as bdoctorAttendingBirth, btr.createdtime as bcreatedtime, btr.lastmodifiedtime as blastmodifiedtime, ";

    private static final String ADDRESS_SELECT_QUERY = " add.id as aid, add.tenantid as atenantid, add.doorno as adoorno, add.latitude as alatitude, add.longitude as alongitude, add.buildingname as abuildingname, add.addressid as aaddressid, add.addressnumber as aaddressnumber, add.type as atype, add.addressline1 as aaddressline1, add.addressline2 as aaddressline2, add.landmark as alandmark, add.street as astreet, add.city as acity, add.locality as alocality, add.pincode as apincode, add.detail as adetail, add.registrationid as aregistrationid ";

    private static final String FROM_TABLES = " FROM eg_bt_registration btr LEFT JOIN eg_bt_address add ON btr.id = add.registrationid ";

    private final String ORDERBY_CREATEDTIME = " ORDER BY btr.createdtime DESC ";

    public String getBirthApplicationSearchQuery(BirthApplicationSearchCriteria criteria, List<Object> preparedStmtList){getSearchURLWithParams(String tenantId, String businessService) {

        StringBuilder url = new StringBuilder(config.getWfHost());
        url.append(config.getWfBusinessServiceSearchPath());
        url.append("?tenantId=");
        url.append(tenantId);
        url.append("&businessServices=");
        url.append(businessService);
        return url;
    }

    public ProcessInstanceRequest getProcessInstanceForBirthRegistrationPayment(BirthRegistrationRequest updateRequest) {

        BirthRegistrationApplication application = updateRequest.getBirthRegistrationApplications().get(0);

        ProcessInstance process = ProcessInstance.builder()
                .businessService("BTR")
                .businessId(application.getApplicationNumber())
                .comment("Payment for birth registration processed")
                .moduleName("birth-services")
                .tenantId(application.getTenantId())
                .action("PAY")
            StringBuilder query = new StringBuilder(BASE_BTR_QUERY.build();

        return queryProcessInstanceRequest.appendbuilder(ADDRESS_SELECT_QUERY);
        query.append(FROM_TABLES);          if(!ObjectUtils.isEmpty(criteria.getTenantId.requestInfo(updateRequest.getRequestInfo())){
                addClauseIfRequired(query, preparedStmtList);.processInstances(Arrays.asList(process))
            query.append(" btr.tenantid = ? ".build();

    }
}

iii) Add the following properties to application.properties file -

Code Block
#Workflow config
is.workflow.enabled=true
egov.workflow.host=http://localhost:8282
egov.workflow.transition.path=/egov-workflow-v2/egov-wf/process/_transition
    preparedStmtList.add(criteria.getTenantId());
        }
        if(!ObjectUtils.isEmpty(criteria.getStatus())){
            addClauseIfRequired(query, preparedStmtList);
            query.append(" btr.status = ? ");egov.workflow.businessservice.search.path=/egov-workflow-v2/egov-wf/businessservice/_search
egov.workflow.processinstance.search.path=/egov-workflow-v2/egov-wf/process/_search
  1. Calculation - The calculation class will contain the calculation logic for given service delivery. Based on the application submitted the calculator class will calculate the tax/charges and call billing service to generate demand.

For our guide, we are going to create a sample calculation class with some dummy logic. For this, we are going to perform the following steps -

i) Create a class under service folder by the name of CalculationService

ii) Now, annotate this class with @Service annotation and add the following logic within it -

Code Block
@Service
public class CalculationService {
    public Double calculateLaminationCharges(BirthRegistrationApplication application){
        // Add calculation  preparedStmtList.add(criteria.getStatus());
        }
   logic according to business requirement
    if(!CollectionUtils.isEmpty(criteria.getIds())){    return 10.0;
    }
}

Repository Layer:

Methods in the service layer, upon performing all the business logic, call methods in the Repository layer to persist or lookup data i.e. it interacts with the configured data store. For executing the queries, JdbcTemplate class is used. JdbcTemplate takes care of creation and release of resources such as creating and closing the connection etc. All database operations namely insert, update, search and delete can be performed on the database using methods of JdbcTemplate class.

On DIGIT however, we handle create and update operations asynchronously. Our persister service listens on the topic to which service applications are pushed for insertion and updation. Persister then takes care of executing insert and update operations on the database without hogging our application’s threads.

That leaves us with execution of search queries on the database to return applications as per the search parameters provided by the user.

For this guide, these are the steps that we will be taking to implement repository layer -

i) Create querybuilder and rowmapper folders within repository folder.

ii) Create a class by the name of BirthApplicationQueryBuilder in querybuilder folder and annotate it with @Component annotation. Put the following content in BirthApplicationQueryBuilder class -

Code Block
@Component
public addClauseIfRequired(query, preparedStmtList);
class BirthApplicationQueryBuilder {

    private static final String BASE_BTR_QUERY = query.append(" SELECT btr.id INas (bid, ").append(createQuery(criteria.getIds())).append(" ) ");
            addToPreparedStatement(preparedStmtList, criteria.getIds());
        }
        if(!ObjectUtils.isEmpty(criteria.getApplicationNumber())){
            addClauseIfRequired(query, preparedStmtList);
            query.append(" btr.applicationnumber = ? ");
            preparedStmtList.add(criteria.getApplicationNumber());
        }

        // order birth registration applications based on their createdtime in latest first manner
        query.append(ORDERBY_CREATEDTIME);

        return query.toString();
    }

    private void addClauseIfRequired(StringBuilder query, List<Object> preparedStmtList){
        if(preparedStmtList.isEmpty()){
            query.append(" WHERE ");
        }else{
            query.append(" AND ");
        }
    }

    private String createQuery(List<String> ids) btr.tenantid as btenantid, btr.applicationnumber as bapplicationnumber, btr.babyfirstname as bbabyfirstname, btr.babylastname as bbabylastname, btr.fathername as bfathername, btr.mothername as bmothername, btr.doctorname as bdoctorname, btr.hospitalname as bhospitalname, btr.placeofbirth as bplaceofbirth, btr.dateofbirth as bdateofbirth, btr.createdtime as bcreatedtime, btr.lastmodifiedtime as blastmodifiedtime, ";

    private static final String ADDRESS_SELECT_QUERY = " add.id as aid, add.tenantid as atenantid, add.doorno as adoorno, add.latitude as alatitude, add.longitude as alongitude, add.buildingname as abuildingname, add.addressid as aaddressid, add.addressnumber as aaddressnumber, add.type as atype, add.addressline1 as aaddressline1, add.addressline2 as aaddressline2, add.landmark as alandmark, add.street as astreet, add.city as acity, add.locality as alocality, add.pincode as apincode, add.detail as adetail, add.registrationid as aregistrationid ";

    private static final String FROM_TABLES = " FROM eg_bt_registration btr LEFT JOIN eg_bt_address add ON btr.id = add.registrationid ";

    private final String ORDERBY_CREATEDTIME = " ORDER BY btr.createdtime DESC ";

    public String getBirthApplicationSearchQuery(BirthApplicationSearchCriteria criteria, List<Object> preparedStmtList){
        StringBuilder builderquery = new StringBuilder(BASE_BTR_QUERY);
        int length = ids.size(query.append(ADDRESS_SELECT_QUERY);
        for (int i = 0; i < length; i++) query.append(FROM_TABLES);

        if(!ObjectUtils.isEmpty(criteria.getTenantId())){
            builder.append(" ?"addClauseIfRequired(query, preparedStmtList);
            if (i != length - 1)query.append(" btr.tenantid = ? ");
                builder.append(","preparedStmtList.add(criteria.getTenantId());
        }
        return builder.toString();if(!ObjectUtils.isEmpty(criteria.getStatus())){
    }      private void addToPreparedStatementaddClauseIfRequired(List<Object>query, preparedStmtList,);
 List<String> ids) {         idsquery.forEach(id -> {append(" btr.status = ? ");
            preparedStmtList.add(idcriteria.getStatus());
        });
        if(!CollectionUtils.isEmpty(criteria.getIds())){
           } }

iii) Next, create a class by the name of BirthApplicationRowMapper under rowmapper folder and annotate it with @Component. Add the following content in the class -

Code Block
breakoutModewide
@Component
public class BirthApplicationRowMapper implements ResultSetExtractor<List<BirthRegistrationApplication>> {
    public List<BirthRegistrationApplication> extractData(ResultSet rs) throws SQLException, DataAccessException {addClauseIfRequired(query, preparedStmtList);
            query.append(" btr.id IN ( ").append(createQuery(criteria.getIds())).append(" ) ");
          Map<String,BirthRegistrationApplication> birthRegistrationApplicationMap = new LinkedHashMap<>();addToPreparedStatement(preparedStmtList, criteria.getIds());
        }
   while (rs.next     if(!ObjectUtils.isEmpty(criteria.getApplicationNumber())){
            addClauseIfRequired(query, preparedStmtList);
     String     uuid = rsquery.getStringappend("bapplicationnumber btr.applicationnumber = ? ");
            BirthRegistrationApplication birthRegistrationApplication = birthRegistrationApplicationMap.get(uuid);preparedStmtList.add(criteria.getApplicationNumber());
        }

     if(birthRegistrationApplication == null) {// order birth registration applications based on their createdtime in latest first manner
     Long lastModifiedTime = rsquery.getLong("blastModifiedTime"append(ORDERBY_CREATEDTIME);

               if (rs.wasNull()) {return query.toString();
    }

    private void addClauseIfRequired(StringBuilder query, List<Object> preparedStmtList){
     lastModifiedTime = null;
 if(preparedStmtList.isEmpty()){
            query.append(" WHERE ");
}        }else{
         Applicant applicant = Applicant.builder().id(rs.getString("bapplicantid")).build(); query.append(" AND ");
        }
    }

   AuditDetails auditdetailsprivate =String AuditDetails.builder(createQuery(List<String> ids) {
        StringBuilder builder = new StringBuilder();
        int length .createdBy(rs.getString("bcreatedBy"))= ids.size();
        for (int i = 0; i < length; i++) {
       .createdTime(rs.getLong("bcreatedTime"))     builder.append(" ?");
            if (i != length - 1)
             .lastModifiedBy(rs.getString   builder.append("blastModifiedBy,"));
        }
        return builder.toString();
    }

    private .lastModifiedTime(lastModifiedTimevoid addToPreparedStatement(List<Object> preparedStmtList, List<String> ids) {
        ids.forEach(id -> {
            preparedStmtList.buildadd(id);
        });
    }
}

iii) Next, create a class by the name of BirthApplicationRowMapper under rowmapper folder and annotate it with @Component. Add the following content in the class -

Code Block
@Component
public class BirthApplicationRowMapper birthRegistrationApplicationimplements = BirthRegistrationApplication.builder()ResultSetExtractor<List<BirthRegistrationApplication>> {
    public List<BirthRegistrationApplication> extractData(ResultSet rs) throws SQLException, DataAccessException {
        Map<String,BirthRegistrationApplication> birthRegistrationApplicationMap =  .applicationNumber(rs.getString("bapplicationnumber"))new LinkedHashMap<>();

        while (rs.next()){
            String uuid .tenantId(= rs.getString("btenantidbapplicationnumber"));
            BirthRegistrationApplication birthRegistrationApplication = birthRegistrationApplicationMap.get(uuid);

           .id(rs.getString("bid"))
   if(birthRegistrationApplication == null) {

                Long lastModifiedTime =  .assemblyConstituency(rs.getStringgetLong("bassemblyconstituencyblastModifiedTime"));
                        .dateSinceResidenceif (rs.getIntwasNull("bdatesinceresidence")) {
                    lastModifiedTime = null;
.applicant(applicant)                }

       .auditDetails(auditdetails)      //   Applicant applicant = Applicant.builder().id(rs.getString("bapplicantid")).build();

           .build();     AuditDetails auditdetails = AuditDetails.builder()
    }             addChildrenToProperty(rs, birthRegistrationApplication);       .createdBy(rs.getString("bcreatedBy"))
     birthRegistrationApplicationMap.put(uuid, birthRegistrationApplication);         }         return new ArrayList<>.createdTime(birthRegistrationApplicationMaprs.valuesgetLong("bcreatedTime"));
       }      private void addChildrenToProperty(ResultSet rs, BirthRegistrationApplication birthRegistrationApplication)      .lastModifiedBy(rs.getString("blastModifiedBy"))
      throws SQLException {         addAddressToApplication(rs, birthRegistrationApplication);     } .lastModifiedTime(lastModifiedTime)
    private void addAddressToApplication(ResultSet rs, BirthRegistrationApplication birthRegistrationApplication) throws SQLException {         Address address = Address.builderbuild();

                birthRegistrationApplication = BirthRegistrationApplication.builder()
                 .id       .applicationNumber(rs.getString("aidbapplicationnumber"))
                                    .tenantId(rs.getString("atenantidbtenantid"))
                        .id(rs.getString("bid"))
            .doorNo(rs.getString("adoorno"))            .placeOfBirth(rs.getString("bplaceofbirth"))
                        .latitudetimeOfBirth(rs.getDoublegetInt("alatitudebtimeofbirth"))
                        .auditDetails(auditdetails)
             .longitude(rs.getDouble("alongitude"))           .build();
            }
            .buildingNameaddChildrenToProperty(rs.getString("abuildingname")), birthRegistrationApplication);
            birthRegistrationApplicationMap.put(uuid, birthRegistrationApplication);
        }
        return new    .addressId(rs.getString("aaddressid"))ArrayList<>(birthRegistrationApplicationMap.values());
    }

    private void addChildrenToProperty(ResultSet rs, BirthRegistrationApplication birthRegistrationApplication)
            throws SQLException {
     .addressNumber(rs.getString("aaddressnumber"))   addAddressToApplication(rs, birthRegistrationApplication);
    }

    private void addAddressToApplication(ResultSet rs, BirthRegistrationApplication birthRegistrationApplication) throws SQLException {
        Address address =   Address.type(rs.getString("atype")builder()
                   .id(rs.getString("aid"))
                .addressLine1tenantId(rs.getString("aaddressline1atenantid"))
                     .doorNo(rs.getString("adoorno"))
                .addressLine2latitude(rs.getStringgetDouble("aaddressline2alatitude"))
                .longitude(rs.getDouble("alongitude"))
                   .landmarkbuildingName(rs.getString("alandmarkabuildingname"))
                   .addressId(rs.getString("aaddressid"))
                .streetaddressNumber(rs.getString("astreetaaddressnumber"))
                   .type(rs.getString("atype"))
                .cityaddressLine1(rs.getString("acityaaddressline1"))
                .addressLine2(rs.getString("aaddressline2"))
                   .pincodelandmark(rs.getString("apincodealandmark"))
                .street(rs.getString("astreet"))
                   .detail.city(rs.getString("adetailacity"))
                 .pincode(rs.getString("apincode"))
                  .registrationIddetail("aregistrationidadetail")
                  .registrationId("aregistrationid")
                 .build();


        birthRegistrationApplication.setAddress(address);

    }

}

iv) Finally, create a class by the name of BirthRegistrationRepository under repository folder and annotate it with @Repository annotation. Put the following content into the class -

Code Block
@Slf4j
@Repository
public class BirthRegistrationRepository {

    @Autowired
    private BirthApplicationQueryBuilder queryBuilder;

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private BirthApplicationRowMapper rowMapper;

    public List<BirthRegistrationApplication> getApplicationsList<BirthRegistrationApplication>getApplications(BirthApplicationSearchCriteria searchCriteria){
        List<Object> preparedStmtList = new ArrayList<>();
        String query = queryBuilder.getBirthApplicationSearchQuery(searchCriteria, preparedStmtList);
        log.info("Final query: " + query);
        return jdbcTemplate.query(query, preparedStmtList.toArray(), rowMapper);
     }
}

Producer:

Producer classes help in pushing data from the application to kafka topics. For this, we have a custom implementation of KafkaTemplate class in our tracer library called CustomKafkaTemplate. This implementation of producer class does not change across services of DIGIT. Producer implementation can be viewed here - Producer Implementation

...

Code Block
@Service
@Slf4j
public class Producer {

    @Autowired
    private CustomKafkaTemplate<String, Object> kafkaTemplate;

    public void push(String topic, Object value) {
        kafkaTemplate.send(topic, value);
    }
} 

Consumers:

Customized SMS creation: Once an application is created/updated the data is pushed on kafka topic. We trigger notification by consuming data from this topic. Whenever any message is consumed the service will call the localisation service to fetch the SMS template. It will then replace the placeholders in the SMS template with the values in the message it consumed(For example: It will replace the {NAME} placeholder with owner name from the data consumed). Once the SMS text is ready, the service will push this data(Create the SMSRequest object which is part of common modules and push the object) on notification topic. (SMS service consumes data from notification topic and triggers SMS).

For our guide, we will be implementing a notification consumer in the following section.

Notification:

Once an application is created/requested or progresses further in the workflow, notifications can be triggered as each of these events are pushed onto kafka topics which can be listened on and a sms/email/in-app notification can be sent to the concerned user(s).

...

Code Block
@Component
@Slf4j
public class NotificationConsumer {

    @Autowired
    private ObjectMapper mapper;

    @Autowired
    private NotificationService notificationService;

    @KafkaListener(topics = {"${egov.bt.registration.create.topic}"})
    public void listen(final HashMap<String, Object> record, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {

        try {

            BirthRegistrationRequest request = mapper.convertValue(record, BirthRegistrationRequest.class);
            //log.info(request.toString());
            notificationService.prepareEventAndSend(request);

        } catch (final Exception e) {

            log.error("Error while listening to value: " + record + " on topic: " + topic + ": ", e);
        }
    }
   

}

Create a POJO by the name of SMSRequest under models folder and add the following content into it -

...

Code Block
@Slf4j
@Service
public class NotificationService {

    @Autowired
    private Producer producer;

    @Autowired
    private BTRConfiguration config;

    @Autowired
    private RestTemplate restTemplate;

    private static final String smsTemplate = "Dear {NAME}, your birth registration application has been successfully created on the system with application number - {APPNUMBER}.";

    public void prepareEventAndSend(BirthRegistrationRequest request){
        List<SMSRequest> smsRequestList = new ArrayList<>();
        request.getBirthRegistrationApplications().forEach(application -> {
            SMSRequest smsRequest = SMSRequest.builder().mobileNumber(application.getApplicantgetApplicantMobileNumber().getMobileNumber()).message(getCustomMessage(smsTemplate, application)).build();
            smsRequestList.add(smsRequest);
        });
        for (SMSRequest smsRequest : smsRequestList) {
            producer.push(config.getSmsNotificationTopic(), smsRequest);
            log.info("Messages: " + smsRequest.getMessage());
        }
    }

    private String getCustomMessage(String template, BirthRegistrationApplication application) {
        template = template.replace("{APPNUMBER}", application.getApplicationNumber());
        template = template.replace("{NAME}", application.getApplicant().getNamegetFatherName());
        return template;
    }

}

Persister:

...

Payment Backupdate:

Once payment is done the application status has to be updated. Since we have a microservice architecture the two services can communicate with each other either through API calls or using message queues(kafka in our case). To avoid any service specific code in collection service we use the second approach to notify the service of payment for its application. Whenever a payment is done the collection service will publish the payment details on a kafka topic. Any microservice which wants to get notified when payments are done can subscribe to this topic. Once the service consumes the payment message it will check if the payment is done for its service by checking the businessService code. If it is done for the given service it will update the application status to PAID or will trigger workflow action PAY depending on the use case.

...

Code Block
@Component
public class PaymentBackUpdateConsumer {

    @Autowired
    private PaymentUpdateService paymentUpdateService;

    @KafkaListener(topics = {"${kafka.topics.receipt.create}"})
    public void listenPayments(final HashMap<String, Object> record, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {
        paymentUpdateService.process(record);
    }
}

ii) Next, under service folder create a new class by the name of PaymentUpdateService and annotate it with @Service. Put the following content in this class -

Code Block
@Slf4j
@Service
public class PaymentUpdateService {

    @Autowired
    private WorkflowService workflowService;

    @Autowired
    private ObjectMapper mapper;

    @Autowired
    private BirthRegistrationRepository repository;

    public void process(HashMap<String, Object> record) {

        try {

            PaymentRequest paymentRequest = mapper.convertValue(record, PaymentRequest.class);
            RequestInfo requestInfo = paymentRequest.getRequestInfo();

            List<PaymentDetail> paymentDetails = paymentRequest.getPayment().getPaymentDetails();
            String tenantId = paymentRequest.getPayment().getTenantId();

            for (PaymentDetail paymentDetail : paymentDetails) {
                updateWorkflowForBirthRegistrationPayment(requestInfo, tenantId, paymentDetail);
            }
        } catch (Exception e) {
            log.error("KAFKA_PROCESS_ERROR:", e);
        }

    }

    private void updateWorkflowForBirthRegistrationPayment(RequestInfo requestInfo, String tenantId, PaymentDetail paymentDetail) {

        Bill bill  = paymentDetail.getBill();

        BirthApplicationSearchCriteria criteria = BirthApplicationSearchCriteria.builder()
                .applicationNumber(bill.getConsumerCode())
                .tenantId(tenantId)
                .build();

        List<BirthRegistrationApplication> birthRegistrationApplicationList = repository.getApplications(criteria);

        if (CollectionUtils.isEmpty(birthRegistrationApplicationList))
            throw new CustomException("INVALID RECEIPT",
                    "No applications found for the consumerCode " + criteria.getApplicationNumber());

        Role role = Role.builder().code("SYSTEM_PAYMENT").tenantId(tenantId).build();
        requestInfo.getUserInfo().getRoles().add(role);

        birthRegistrationApplicationList.forEach( application -> {

            BirthRegistrationRequest updateRequest = BirthRegistrationRequest.builder().requestInfo(requestInfo)
                    .birthRegistrationApplications(Collections.singletonList(application)).build();

            ProcessInstanceRequest wfRequest = workflowService.getProcessInstanceForBirthRegistrationPayment(updateRequest);

            State state = workflowService.callWorkFlow(wfRequest);

        });
    }

}

iii) Create the following POJOs under models folder -

Code Block
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PaymentRequest {

    @NotNull
    @Valid
    @JsonProperty("RequestInfo")
    private RequestInfo requestInfo;

    @NotNull
    @Valid
    @JsonProperty("Payment")
    private Payment payment;

}
Code Block
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode
public class Payment {

    	@Size(max = 64)
  	  @JsonProperty("id")
 	   private String id;

    	@NotNull
  	  @Size(max = 64))
    	@JsonProperty("tenantId")
    	private String tenantId;

    	@JsonProperty("totalDue")
   	 private BigDecimal totalDue;

    	@NotNull
 	   @JsonProperty("totalAmountPaid")
	    private BigDecimal totalAmountPaid;

    	@Size(max = 128)
 	   @JsonProperty("transactionNumber")
    	private String transactionNumber;

    	@JsonProperty("transactionDate")
 	   private Long transactionDate;

	@NotNull
	    @NotNull
    @JsonProperty("paymentMode")
 	   private String paymentMode;

    	@JsonProperty("instrumentDate")
    	private Long instrumentDate;

 	   @Size(max = 128)
    	@JsonProperty("instrumentNumber")
    	private String instrumentNumber;

 	   @JsonProperty("instrumentStatus")
    	private String instrumentStatus;

    	@Size(max = 64)
 	   @JsonProperty("ifscCode")
	    private String ifscCode;

    	@JsonProperty("auditDetails")
 	   private AuditDetails auditDetails;

    	@JsonProperty("additionalDetails")
    	private JsonNode additionalDetails;

 	   @JsonProperty("paymentDetails")
    	@Valid
    	private List<PaymentDetail> paymentDetails;

 	   @Size(max = 128)
    	@NotNull
    	@JsonProperty("paidBy")
 	   private String paidBy;

    	@Size(max = 64)
    	@NotNull
 	   @JsonProperty("mobileNumber")
    	private String mobileNumber;

    	@Size(max = 128)
 	   @JsonProperty("payerName")
    	private String payerName;

    	@Size(max = 1024)
 	   @JsonProperty("payerAddress")
    	private String payerAddress;

    	@Size(max = 64)
 	   @JsonProperty("payerEmail"))
    	private String payerEmail;

    	@Size(max = 64)
    	@JsonProperty("payerId")
  	  private String payerId;

    	@JsonProperty("paymentStatus")
	    private String paymentStatus;

	    public Payment addpaymentDetailsItem(PaymentDetail paymentDetail) {
		
        if (this.paymentDetails == null) {
			
            this.paymentDetails = new ArrayList<>();
		}
		        }
        this.paymentDetails.add(paymentDetail);
		
        return this;
    	}

}
Code Block
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode
public class PaymentDetail {

    @Size(max = 64)
    @JsonProperty("id")
    private String id;

    @Size(max = 64)
    @JsonProperty("tenantId")
    private String tenantId;

    @JsonProperty("totalDue")
    private BigDecimal totalDue;

    @NotNull
    @JsonProperty("totalAmountPaid")
    private BigDecimal totalAmountPaid;

    @Size(max = 64)
    @JsonProperty("receiptNumber")
    private String receiptNumber;

    @Size(max = 64)
    @JsonProperty("manualReceiptNumber")
    private String manualReceiptNumber;

    @JsonProperty("manualReceiptDate")
    private Long manualReceiptDate;

    @JsonProperty("receiptDate")
    private Long receiptDate;

    @JsonProperty("receiptType")
    private String receiptType;

    @JsonProperty("businessService")
    private String businessService;

    @NotNull
    @Size(max = 64)
    @JsonProperty("billId")
    private String billId;

    @JsonProperty("bill")
    private Bill bill;

    @JsonProperty("additionalDetails")
    private JsonNode additionalDetails;

    @JsonProperty("auditDetails")
    private AuditDetails auditDetails;

}
Code Block
@Getter
@Setter
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class Bill {
 

  	  @JsonProperty("id")
	    private String id;

 	   @JsonProperty("mobileNumber")
  	  private String mobileNumber;

  	  @JsonProperty("paidBy")
  	  private String paidBy;

	    @JsonProperty("payerName")
 	   private String payerName;

	    @JsonProperty("payerAddress")
  	  private String payerAddress;

  	  @JsonProperty("payerEmail")
	    private String payerEmail;

 	   @JsonProperty("payerId")
  	  private String payerId;

  	  @JsonProperty("status")
  	  private StatusEnum status;

	    @JsonProperty("reasonForCancellation")
  	  private String reasonForCancellation;

	    @JsonProperty("isCancelled")
  	  private Boolean isCancelled;

  	  @JsonProperty("additionalDetails")
 	   private JsonNode additionalDetails;

  	  @JsonProperty("billDetails")
	    @Valid
  	  private List<BillDetail> billDetails;

  	  @JsonProperty("tenantId")
 	   private String tenantId;

  	  @JsonProperty("auditDetails")
	    private AuditDetails auditDetails;

  	  @JsonProperty("collectionModesNotAllowed")
  	  private List<String> collectionModesNotAllowed;

 	   @JsonProperty("partPaymentAllowed")
  	  private Boolean partPaymentAllowed;

	    @JsonProperty("isAdvanceAllowed")
  	  private Boolean isAdvanceAllowed;

  	  @JsonProperty("minimumAmountToBePaid")
 	   private BigDecimal minimumAmountToBePaid;

  	  @JsonProperty("businessService")
	    private String businessService;

  	  @JsonProperty("totalAmount")
  	  private BigDecimal totalAmount;

 	   @JsonProperty("consumerCode")
  	  private String consumerCode;

	    @JsonProperty("billNumber")
  	  private String billNumber;

  	  @JsonProperty("billDate")
 	   private Long billDate;

  	  @JsonProperty("amountPaid"))
  	  private BigDecimal amountPaid;



    	public enum StatusEnum {
      	  ACTIVE("ACTIVE"),

 	       CANCELLED("CANCELLED"),

  	      PAID("PAID"),

	        EXPIRED("EXPIRED");

   	     private String value;

      	  StatusEnum(String value) {
	
            this.value = value;
	}        }


        	@Override
	@JsonValue
	
        @JsonValue
        public String toString() {
		
            return String.valueOf(value);
	
        }

	        public static boolean contains(String test) {
		
            for (StatusEnum val : StatusEnum.values()) {
			
                if (val.name().equalsIgnoreCase(test)) {
				return true;
			}
		}
		return false;
	}

	@JsonCreator
	 {
                    return true;
                }
            }
            return false;
        }

        @JsonCreator
        public static StatusEnum fromValue(String text) {
		
            for (StatusEnum b : StatusEnum.values()) {
			
                if (String.valueOf(b.value).equals(text)) {
				
                    return b;
			}
		}
		
                }
            }
            return null;
	}
        }

    }

    	public Boolean addBillDetail(BillDetail billDetail) {

  		      if (CollectionUtils.isEmpty(billDetails)) {

       			     billDetails = new ArrayList<>();
			
            return billDetails.add(billDetail);
		
        } else {

            			if (!billDetails.contains(billDetail))
				
                return billDetails.add(billDetail);
			else
				
            else
                return false;
		}
	
        }
    }


}
Code Block
@Setter
@Getter
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(of = { "id" })
public class BillDetail {

    @JsonProperty("id")
    private String id;

    @JsonProperty("tenantId")
    private String tenantId;

    @JsonProperty("demandId")
    private String demandId;

    @JsonProperty("billId")
    private String billId;

    @JsonProperty("amount")
    @NotNull
    private BigDecimal amount;

    @JsonProperty("amountPaid")
    private BigDecimal amountPaid;

    @NotNull
    @JsonProperty("fromPeriod")
    private Long fromPeriod;

    @NotNull
    @JsonProperty("toPeriod")
    private Long toPeriod;

    @JsonProperty("additionalDetails")
    private JsonNode additionalDetails;

    @JsonProperty("channel")
    private String channel;

    @JsonProperty("voucherHeader")
    private String voucherHeader;

    @JsonProperty("boundary")
    private String boundary;

    @JsonProperty("manualReceiptNumber")
    private String manualReceiptNumber;

    @JsonProperty("manualReceiptDate")
    private Long manualReceiptDate;

    @JsonProperty("billAccountDetails")
    private List<BillAccountDetail> billAccountDetails;

    @NotNull
    @JsonProperty("collectionType")
    private String collectionType;

    @JsonProperty("auditDetails")
    private AuditDetails auditDetails;

    private String billDescription;

    @NotNull
    @JsonProperty("expiryDate")
    private Long expiryDate;
 


      public Boolean addBillAccountDetail(BillAccountDetail billAccountDetail) {

        if (CollectionUtils.isEmpty(billAccountDetails)) {

            billAccountDetails = new ArrayList<>();
            return billAccountDetails.add(billAccountDetail);
        } else {

            if (!billAccountDetails.contains(billAccountDetail))
                return billAccountDetails.add(billAccountDetail);
            else
                return false;
        }
    }

}
Code Block
@Setter
@Getter
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class BillAccountDetail {

 	   @Size(max = 64)
	    @JsonProperty("id")
  	  private String id;

 	   @Size(max = 64)
    	@JsonProperty("tenantId")
  	  private String tenantId;

 	   @Size(max = 64)
    	@JsonProperty("billDetailId")
  	  private String billDetailId;

 	   @Size(max = 64)
    	@JsonProperty("demandDetailId")
  	  private String demandDetailId;

 	   @JsonProperty("order")
	    private Integer order;

  	  @JsonProperty("amount")
 	   private BigDecimal amount;

    	@JsonProperty("adjustedAmount")
  	  private BigDecimal adjustedAmount;

 	   @JsonProperty("isActualDemand")
    	private Boolean isActualDemand;

  	  @Size(max = 64)
 	   @JsonProperty("taxHeadCode")
    	private String taxHeadCode;

  	  @JsonProperty("additionalDetails")
 	   private JsonNode additionalDetails;

    	@JsonProperty("auditDetails")
  	  private AuditDetails auditDetails;
}

Persister configurations:

The persister configuration is written in a YAML format. The INSERT and UPDATE queries for each table is added in prepared Statement format, followed by the jsonPaths of values which has to be inserted/updated.

...

Code Block
serviceMaps:
  serviceName: btr-services
  mappings:
    - version: 1.0
      description: Persists birth details in tables
      fromTopic: save-bt-application
      isTransaction: true
      queryMaps:

        - query: INSERT INTO eg_bt_registration(id,tenantid,applicationNumber,babyFirstName,applicationnumberbabyLastName,babyLastNamefatherName,motherName,fatherName,doctorAttendingBirthdoctorName,hospitalName,placeOfBirth,dateOfBirth,createdtime, lastmodifiedtime) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
          basePath: BirthRegistrationApplications.*
          jsonMaps:
            - jsonPath: $.BirthRegistrationApplications.*.id

            - jsonPath: $.BirthRegistrationApplications.*.tenantId

            - jsonPath: $.BirthRegistrationApplications.*.babyFirstNameapplicationNumber
          
              - jsonPath: $.BirthRegistrationApplications.*.applicationNumberbabyFirstName
            
            - jsonPath: $.BirthRegistrationApplications.*.babyLastName
          
              - jsonPath: $.BirthRegistrationApplications.*.motherNamefatherName
                                   
            - jsonPath: $.BirthRegistrationApplications.*.fatherNamemotherName
            
            - jsonPath: $.BirthRegistrationApplications.*.doctorAttendingBirthdoctorName
            
            - jsonPath: $.BirthRegistrationApplications.*.hospitalName
            
            - jsonPath: $.BirthRegistrationApplications.*.placeOfBirth

            - jsonPath: $.BirthRegistrationApplications.*.dateOfBirth

            - jsonPath: $.BirthRegistrationApplications.*.auditDetails.createdTime

            - jsonPath: $.BirthRegistrationApplications.*.auditDetails.lastModifiedTime

        - query: INSERT INTO eg_bt_address(id, tenantid, doorno, latitude, longitude, buildingname, addressid, addressnumber, type, addressline1, addressline2, landmark, street, city, locality, pincode, detail, registrationid, createdby, lastmodifiedby, createdtime, lastmodifiedtime) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
          basePath: BirthRegistrationApplications.*
          jsonMaps:
            - jsonPath: $.BirthRegistrationApplications.*.address.id

            - jsonPath: $.BirthRegistrationApplications.*.address.tenantId

            - jsonPath: $.BirthRegistrationApplications.*.address.doorNo

            - jsonPath: $.BirthRegistrationApplications.*.address.latitude

            - jsonPath: $.BirthRegistrationApplications.*.address.longitude

            - jsonPath: $.BirthRegistrationApplications.*.address.buildingName

            - jsonPath: $.BirthRegistrationApplications.*.address.addressId

            - jsonPath: $.BirthRegistrationApplications.*.address.addressNumber

            - jsonPath: $.BirthRegistrationApplications.*.address.type

            - jsonPath: $.BirthRegistrationApplications.*.address.addressLine1

            - jsonPath: $.BirthRegistrationApplications.*.address.addressLine2

            - jsonPath: $.BirthRegistrationApplications.*.address.landmark

            - jsonPath: $.BirthRegistrationApplications.*.address.street

            - jsonPath: $.BirthRegistrationApplications.*.address.city

            - jsonPath: $.BirthRegistrationApplications.*.address.locality.name

            - jsonPath: $.BirthRegistrationApplications.*.address.pincode

            - jsonPath: $.BirthRegistrationApplications.*.address.detail

            - jsonPath: $.BirthRegistrationApplications.*.address.registrationId

            - jsonPath: $.BirthRegistrationApplications.*.address.createdBy

            - jsonPath: $.BirthRegistrationApplications.*.address.lastModifiedBy

            - jsonPath: $.BirthRegistrationApplications.*.address.createdTime

            - jsonPath: $.BirthRegistrationApplications.*.address.lastModifiedTime

    - version: 1.0
      description: Update birth registration applications in table
      fromTopic: update-bt-application
      isTransaction: true
      queryMaps:
        - query: UPDATE eg_bt_registration SET tenantid = ?,babyFirstName = ?, dateOfBirth = ? WHERE id=?;
          basePath: BirthRegistrationApplications.*
          jsonMaps:
            - jsonPath: $.BirthRegistrationApplications.*.tenantId

            - jsonPath: $.BirthRegistrationApplications.*.babyFirstName

            - jsonPath: $.BirthRegistrationApplications.*.dateOfBirth

            - jsonPath: $.BirthRegistrationApplications.*.id

Indexer Configuration:

Indexer is designed to perform all the indexing tasks of the digit platform. The service reads records posted on specific kafka topics and picks the corresponding index configuration from the yaml file provided by the respective module configuration. Configurations are yaml based. Detailed guide to create indexer configs are mentioned in the following document - Indexer Configuration Guide .

...

Code Block
ServiceMaps:
  serviceName: Birth Registration Service
  version: 1.0.0
  mappings:
    - topic: save-bt-application
      configKey: INDEX
      indexes:
        - name: btindex-v1
          type: general
          id: $.id
          isBulk: true
          timeStampField: $.auditDetails.createdTime
          jsonPath: $.BirthRegistrationApplications
          customJsonMapping:
            indexMapping: {"Data":{"birthapplication":{},"history":{}}}
            fieldMapping:
              - inJsonPath: $
                outJsonPath: $.Data.birthapplication
            externalUriMapping:
              - path: http://localhost:8282/egov-workflow-v2/egov-wf/process/_search
                queryParam: businessIds=$.applicationNumber,history=true,tenantId=$.tenantId
                apiRequest: {"RequestInfo":{"apiId":"org.egov.pt","ver":"1.0","ts":1502890899493,"action":"asd","did":"4354648646","key":"xyz","msgId":"654654","requesterId":"61","authToken":"d9994555-7656-4a67-ab3a-a952a0d4dfc8","userInfo":{"id":1,"uuid":"1fec8102-0e02-4d0a-b283-cd80d5dab067","type":"EMPLOYEE","tenantId":"pb.amritsar","roles":[{"name":"Employee","code":"EMPLOYEE","tenantId":"pb.amritsar"}]}}}
                uriResponseMapping:
                  - inJsonPath: $.ProcessInstances
                    outJsonPath: $.Data.history

    - topic: update-bt-application
      configKey: INDEX
      indexes:
        - name: btindex-v1
          type: general
          id: $.id
          isBulk: true
          timeStampField: $.auditDetails.createdTime
          jsonPath: $.BirthRegistrationApplications
          customJsonMapping:
            indexMapping: {"Data":{"tradelicense":{},"history":{}}}
            fieldMapping:
              - inJsonPath: $
                outJsonPath: $.Data.tradelicense
            externalUriMapping:
              - path: http://localhost:8282/egov-workflow-v2/egov-wf/process/_search
                queryParam: businessIds=$.applicationNumber,history=true,tenantId=$.tenantId
                apiRequest: {"RequestInfo":{"apiId":"org.egov.pt","ver":"1.0","ts":1502890899493,"action":"asd","did":"4354648646","key":"xyz","msgId":"654654","requesterId":"61","authToken":"d9994555-7656-4a67-ab3a-a952a0d4dfc8","userInfo":{"id":1,"uuid":"1fec8102-0e02-4d0a-b283-cd80d5dab067","type":"EMPLOYEE","tenantId":"pb.amritsar","roles":[{"name":"Employee","code":"EMPLOYEE","tenantId":"pb.amritsar"}]}}}
                uriResponseMapping:
                  - inJsonPath: $.ProcessInstances
                    outJsonPath: $.Data.history

 

Certificate Generation:

The final step in this process is the creation of configs to create a birth registration PDF for the citizens to download. For this, we will make use of DIGIT’s PDF service which uses PDFMake and Mustache libraries to generate PDF. A detailed documentation on PDF service and generating PDFs using PDF service can be found here - PDF Generation Service.

...