Skip to content

Commit

Permalink
Merge pull request #867 from egovernments/hrms-fuzzy
Browse files Browse the repository at this point in the history
Hrms fuzzy
  • Loading branch information
pradeepkumarcm-egov authored Aug 8, 2024
2 parents f9c815c + 8af8e68 commit 9b6d4cf
Show file tree
Hide file tree
Showing 12 changed files with 503 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

import javax.net.ssl.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

@SpringBootApplication
@ComponentScan(basePackages = { "org.egov.hrms", "org.egov.hrms.web.controllers" , "org.egov.hrms.config"})
@Import(TracerConfiguration.class)
Expand All @@ -76,7 +80,36 @@ public ObjectMapper getObjectMapper() {
return objectMapper;
}

public static void trustSelfSignedSSL() {
try {
SSLContext ctx = SSLContext.getInstance("TLS");
X509TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException {
}

public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException {
}

public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
ctx.init(null, new TrustManager[]{tm}, null);
SSLContext.setDefault(ctx);

// Disable hostname verification
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
public boolean verify(String hostname, javax.net.ssl.SSLSession sslSession) {
return true;
}
});
} catch (Exception ex) {
ex.printStackTrace();
}
}

public static void main(String[] args) {
trustSelfSignedSSL();
SpringApplication.run(EgovEmployeeApplication.class, args);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,39 @@ public class PropertiesManager {

@Value("${egov.hrms.max.pagination.limit}")
public Integer hrmsMaxLimit;

// FuzzyConfigs
@Value("${hrms.search.pagination.default.limit}")
public Long defaultLimit;

@Value("${hrms.search.pagination.default.offset}")
public Long defaultOffset;

@Value("${hrms.search.pagination.max.search.limit}")
public Long searchLimit;

@Value("${hrms.search.pagination.max.search.limit}")
private Long maxSearchLimit;

@Value("${hrms.fuzzy.search.is.wildcard}")
private Boolean isSearchWildcardBased;

@Value("${hrms.search.name.fuziness}")
private String nameFuziness;

// es configs
@Value("${elasticsearch.host}")
private String esHost;

@Value("${hrms.es.index}")
private String esPTIndex;

@Value("${elasticsearch.search.endpoint}")
private String esSearchEndpoint;

@Value("${egov.es.username}")
private String userName;

@Value("${egov.es.password}")
private String password;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package org.egov.hrms.repository;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.egov.hrms.config.PropertiesManager;
import org.egov.hrms.web.contract.EmployeeSearchCriteria;
import org.egov.tracer.model.CustomException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Repository;
import org.springframework.web.client.RestTemplate;

import java.util.Base64;
import java.util.List;

@Slf4j
@Repository
public class ElasticSearchRepository {
private PropertiesManager config;

private FuzzySearchQueryBuilder queryBuilder;

private RestTemplate restTemplate;

private ObjectMapper mapper;

@Autowired
public ElasticSearchRepository(PropertiesManager config, FuzzySearchQueryBuilder queryBuilder, RestTemplate restTemplate, ObjectMapper mapper) {
this.config = config;
this.queryBuilder = queryBuilder;
this.restTemplate = restTemplate;
this.mapper = mapper;
}

public Object fuzzySearchEmployees(EmployeeSearchCriteria criteria, List<String> uuids) {


String url = getESURL();

String searchQuery = queryBuilder.getFuzzySearchQuery(criteria, uuids);

HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", getESEncodedCredentials());
headers.setContentType(MediaType.APPLICATION_JSON);
log.info("Headers: " + headers.toString());
HttpEntity<String> requestEntity = new HttpEntity<>(searchQuery, headers);
ResponseEntity response = null;
try {
response = restTemplate.postForEntity(url, requestEntity, Object.class);

} catch (Exception e) {
e.printStackTrace();
throw new CustomException("ES_ERROR","Failed to fetch data from ES");
}

return response.getBody();

}


/**
* Generates elasticsearch search url from application properties
*
* @return
*/
private String getESURL() {

StringBuilder builder = new StringBuilder(config.getEsHost());
builder.append(config.getEsPTIndex());
builder.append(config.getEsSearchEndpoint());

return builder.toString();
}

public String getESEncodedCredentials() {
String credentials = config.getUserName() + ":" + config.getPassword();
byte[] credentialsBytes = credentials.getBytes();
byte[] base64CredentialsBytes = Base64.getEncoder().encode(credentialsBytes);
return "Basic " + new String(base64CredentialsBytes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public String paginationClause(EmployeeSearchCriteria criteria, StringBuilder bu
pagination = pagination.replace("$offset", "0");

if(null != criteria.getLimit()){
Integer limit = criteria.getLimit() + criteria.getOffset();
Long limit = criteria.getLimit() + criteria.getOffset();
pagination = pagination.replace("$limit", limit.toString());
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public List<Employee> fetchEmployees(EmployeeSearchCriteria criteria, RequestInf
return employees;
}

private List<String> fetchEmployeesforAssignment(EmployeeSearchCriteria criteria, RequestInfo requestInfo) {
public List<String> fetchEmployeesforAssignment(EmployeeSearchCriteria criteria, RequestInfo requestInfo) {
List<String> employeesIds = new ArrayList<>();
List <Object> preparedStmtList = new ArrayList<>();
String query = queryBuilder.getAssignmentSearchQuery(criteria, preparedStmtList);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package org.egov.hrms.repository;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.egov.hrms.config.PropertiesManager;
import org.egov.hrms.web.contract.EmployeeSearchCriteria;
import org.egov.tracer.model.CustomException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.util.CollectionUtils;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

@Repository
@Slf4j
public class FuzzySearchQueryBuilder {
private ObjectMapper mapper;

private PropertiesManager config;

@Autowired
public FuzzySearchQueryBuilder(ObjectMapper mapper, PropertiesManager config) {
this.mapper = mapper;
this.config = config;
}

private static final String BASE_QUERY = "{\n" +
" \"from\": {{OFFSET}},\n" +
" \"size\": {{LIMIT}},\n" +
" \"sort\": {\n" +
" \"_score\": {\n" +
" \"order\": \"desc\"\n" +
" }\n" +
" },\n" +
" \"query\": {\n" +
" }\n" +
"}";

private static final String fuzzyQueryTemplate = "{\n" +
" \"match\": {\n" +
" \"{{VAR}}\": {\n" +
" \"query\": \"{{PARAM}}\",\n" +
" \"fuzziness\": \"{{FUZZINESS}}\"\n" +
" }\n" +
" }\n" +
" }";

private static final String wildCardQueryTemplate = "{\n" +
" \"query_string\": {\n" +
" \"default_field\": \"{{VAR}}\",\n" +
" \"query\": \"*{{PARAM}}*\"\n" +
" }\n" +
" }";

private static final String filterTemplate = "\"filter\": { " +
" }";

public String getFuzzySearchQuery(EmployeeSearchCriteria criteria, List<String> ids) {
String finalQuery = "";

try {
String baseQuery = addPagination(criteria);
JsonNode node = mapper.readTree(baseQuery);
ObjectNode insideMatch = (ObjectNode)node.get("query");
List<JsonNode> fuzzyClauses = new LinkedList<>();

if(criteria.getName() != null){
fuzzyClauses.add(getInnerNode(criteria.getName(),"Data.user.name",config.getNameFuziness()));
}

ArrayNode tenantIdArray = mapper.createArrayNode();
tenantIdArray.add(criteria.getTenantId());
ObjectNode tenantIdFilter = mapper.createObjectNode();
tenantIdFilter.putPOJO("terms", mapper.createObjectNode().putPOJO("Data.tenantId.keyword", tenantIdArray));
fuzzyClauses.add(tenantIdFilter);

if (criteria.getRoles() != null && !criteria.getRoles().isEmpty()) {
ArrayNode roleArray = mapper.createArrayNode();
for (String role : criteria.getRoles()) {
ObjectNode roleNode = mapper.createObjectNode();
roleNode.put("term", mapper.createObjectNode().put("Data.user.roles.code.keyword", role));
roleArray.add(roleNode);
}
ObjectNode rolesBoolNode = mapper.createObjectNode();
rolesBoolNode.putPOJO("bool", mapper.createObjectNode().putPOJO("should", roleArray));
fuzzyClauses.add(rolesBoolNode);

ObjectNode boolQuery = mapper.createObjectNode();
boolQuery.putPOJO("must", fuzzyClauses);
insideMatch.putPOJO("bool", boolQuery);
finalQuery = mapper.writeValueAsString(node);
}
} catch (Exception e) {
log.error("ES_ERROR", e);
throw new CustomException("JSONNODE_ERROR", "Failed to build JSON query for fuzzy search");
}

log.info("finalQuery {}",finalQuery);
return finalQuery;
}

private JsonNode getInnerNode(String param, String var, String fuziness) throws JsonProcessingException {

String template;
if(config.getIsSearchWildcardBased())
template = wildCardQueryTemplate;
else
template = fuzzyQueryTemplate;
String innerQuery = template.replace("{{PARAM}}",getEscapedString(param));
innerQuery = innerQuery.replace("{{VAR}}",var);

if(!config.getIsSearchWildcardBased())
innerQuery = innerQuery.replace("{{FUZZINESS}}", fuziness);

JsonNode innerNode = mapper.readTree(innerQuery);
return innerNode;
}

private String addPagination(EmployeeSearchCriteria criteria) {

Long limit = config.getDefaultLimit();
Long offset = config.getDefaultOffset();

if (criteria.getLimit() != null && criteria.getLimit() <= config.getMaxSearchLimit())
limit = criteria.getLimit();

if (criteria.getLimit() != null && criteria.getLimit() > config.getMaxSearchLimit())
limit = config.getMaxSearchLimit();

if (criteria.getOffset() != null)
offset = criteria.getOffset();

String baseQuery = BASE_QUERY.replace("{{OFFSET}}", offset.toString());
baseQuery = baseQuery.replace("{{LIMIT}}", limit.toString());

return baseQuery;
}

/**
* Escapes special characters in given string
* @param inputString
* @return
*/
private String getEscapedString(String inputString){
final String[] metaCharacters = {"\\","/","^","$","{","}","[","]","(",")",".","*","+","?","|","<",">","-","&","%"};
for (int i = 0 ; i < metaCharacters.length ; i++) {
if (inputString.contains(metaCharacters[i])) {
inputString = inputString.replace(metaCharacters[i], "\\\\" + metaCharacters[i]);
}
}
return inputString;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ public class EmployeeService {
@Autowired
private ObjectMapper objectMapper;

@Autowired
private FuzzySearchService fuzzySearchService;

/**
* Service method for create employee. Does following:
* 1. Sets ids to all the objects using idgen service.
Expand Down Expand Up @@ -138,11 +141,17 @@ public EmployeeResponse create(EmployeeRequest employeeRequest) {
* @return
*/
public EmployeeResponse search(EmployeeSearchCriteria criteria, RequestInfo requestInfo) {

boolean userChecked = false;
/*if(null == criteria.getIsActive() || criteria.getIsActive())
criteria.setIsActive(true);
else
criteria.setIsActive(false);*/
if(criteria.getName()!=null){
List<Employee> fuzzyEmployees = fuzzySearchService.getEmployees(requestInfo, criteria);
return EmployeeResponse.builder().responseInfo(factory.createResponseInfoFromRequestInfo(requestInfo, true))
.employees(fuzzyEmployees).build();
}
Map<String, User> mapOfUsers = new HashMap<String, User>();
if(!StringUtils.isEmpty(criteria.getPhone()) || !CollectionUtils.isEmpty(criteria.getRoles())) {
Map<String, Object> userSearchCriteria = new HashMap<>();
Expand Down
Loading

0 comments on commit 9b6d4cf

Please sign in to comment.