diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/search/SearchConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/search/SearchConstants.java new file mode 100644 index 000000000..9a6a6b19b --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/search/SearchConstants.java @@ -0,0 +1,98 @@ +package org.mifosplatform.portfolio.search; + +import java.util.HashSet; +import java.util.Set; + +public class SearchConstants { + + public static enum SEARCH_RESPONSE_PARAMETERS { + ENTITY_ID("entityId"), ENTITY_ACCOUNT_NO("entityAccountNo"), ENTITY_EXTERNAL_ID("entityExternalId"), ENTITY_NAME("entityName"), ENTITY_TYPE( + "entityType"), PARENT_ID("parentId"), PARENT_NAME("parentName"); + + private final String value; + + private SEARCH_RESPONSE_PARAMETERS(final String value) { + this.value = value; + } + + private static final Set values = new HashSet(); + static { + for (SEARCH_RESPONSE_PARAMETERS param : SEARCH_RESPONSE_PARAMETERS.values()) { + values.add(param.value); + } + } + + public static Set getAllValues() { + return values; + } + + @Override + public String toString() { + return name().toString().replaceAll("_", " "); + } + + public String getValue() { + return this.value; + } + } + + public static enum SEARCH_SUPPORTED_PARAMETERS { + QUERY("query"), RESOURCE("resource"); + + private final String value; + + private SEARCH_SUPPORTED_PARAMETERS(final String value) { + this.value = value; + } + + private static final Set values = new HashSet(); + static { + for (SEARCH_SUPPORTED_PARAMETERS param : SEARCH_SUPPORTED_PARAMETERS.values()) { + values.add(param.value); + } + } + + public static Set getAllValues() { + return values; + } + + @Override + public String toString() { + return name().toString().replaceAll("_", " "); + } + + public String getValue() { + return this.value; + } + } + + public static enum SEARCH_SUPPORTED_RESOURCES { + CLIENT("client"), GROUP("group"), LOAN("loan"); + + private final String value; + + private SEARCH_SUPPORTED_RESOURCES(final String value) { + this.value = value; + } + + private static final Set values = new HashSet(); + static { + for (SEARCH_SUPPORTED_RESOURCES param : SEARCH_SUPPORTED_RESOURCES.values()) { + values.add(param.value); + } + } + + public static Set getAllValues() { + return values; + } + + @Override + public String toString() { + return name().toString().replaceAll("_", " "); + } + + public String getValue() { + return this.value; + } + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/search/api/SearchApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/search/api/SearchApiResource.java new file mode 100644 index 000000000..3ae4dd414 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/search/api/SearchApiResource.java @@ -0,0 +1,64 @@ +package org.mifosplatform.portfolio.search.api; + +import java.util.Collection; +import java.util.Set; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.UriInfo; + +import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper; +import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; +import org.mifosplatform.infrastructure.core.serialization.ToApiJsonSerializer; +import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.mifosplatform.portfolio.search.SearchConstants.SEARCH_RESPONSE_PARAMETERS; +import org.mifosplatform.portfolio.search.data.SearchConditions; +import org.mifosplatform.portfolio.search.data.SearchData; +import org.mifosplatform.portfolio.search.service.SearchReadPlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Path("/search") +@Component +@Scope("singleton") +public class SearchApiResource { + + private final Set searchResponseParameters = SEARCH_RESPONSE_PARAMETERS.getAllValues(); + + private final PlatformSecurityContext context; + private final SearchReadPlatformService searchReadPlatformService; + private final ToApiJsonSerializer toApiJsonSerializer; + private final ApiRequestParameterHelper apiRequestParameterHelper; + + @Autowired + public SearchApiResource(final PlatformSecurityContext context, final SearchReadPlatformService searchReadPlatformService, final ToApiJsonSerializer toApiJsonSerializer, final ApiRequestParameterHelper apiRequestParameterHelper) { + + this.context = context; + this.searchReadPlatformService = searchReadPlatformService; + this.toApiJsonSerializer = toApiJsonSerializer; + this.apiRequestParameterHelper = apiRequestParameterHelper; + + } + + @GET + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String searchData(@Context final UriInfo uriInfo, @QueryParam("query") final String query, @QueryParam("resource") final String resource) { + + // FIXME - Do we need to validate for permissions + + SearchConditions searchConditions = new SearchConditions(query, resource); + + final Collection searchResults = this.searchReadPlatformService.retriveMatchingData(searchConditions); + + final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, searchResults, searchResponseParameters); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/search/data/SearchConditions.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/search/data/SearchConditions.java new file mode 100644 index 000000000..7c19b714e --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/search/data/SearchConditions.java @@ -0,0 +1,48 @@ +package org.mifosplatform.portfolio.search.data; + +import org.mifosplatform.portfolio.search.SearchConstants.SEARCH_SUPPORTED_RESOURCES; + + +public class SearchConditions { + + private final String searchQuery; + private final String searchResource; + private final Boolean clientSearch; + private final Boolean groupSearch; + private final Boolean loanSeach; + public SearchConditions(String searchQueryParam, String searchResource) { + this.searchQuery = searchQueryParam; + this.searchResource = searchResource; + this.clientSearch = (null == searchResource || searchResource.toLowerCase().contains(SEARCH_SUPPORTED_RESOURCES.CLIENT.name().toLowerCase()))? true : false; + this.groupSearch = (null == searchResource || searchResource.toLowerCase().contains(SEARCH_SUPPORTED_RESOURCES.GROUP.name().toLowerCase()))? true : false; + this.loanSeach = (null == searchResource || searchResource.toLowerCase().contains(SEARCH_SUPPORTED_RESOURCES.LOAN.name().toLowerCase()))? true : false; + } + public SearchConditions(String searchQueryParam, String searchResource, Boolean clientSearch, Boolean groupSearch, Boolean loanSeach) { + this.searchQuery = searchQueryParam; + this.searchResource = searchResource; + this.clientSearch = clientSearch; + this.groupSearch = groupSearch; + this.loanSeach = loanSeach; + } + + public String getSearchQuery() { + return this.searchQuery; + } + + public String getSearchResource() { + return this.searchResource; + } + + public Boolean isClientSearch() { + return this.clientSearch; + } + + public Boolean isGroupSearch() { + return this.groupSearch; + } + + public Boolean isLoanSeach() { + return this.loanSeach; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/search/data/SearchData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/search/data/SearchData.java new file mode 100644 index 000000000..7ad57bc70 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/search/data/SearchData.java @@ -0,0 +1,62 @@ +package org.mifosplatform.portfolio.search.data; + + +public class SearchData { + + private final Long entityId; + private final String entityAccountNo; + private final String entityExternalId; + private final String entityName; + private final String entityType; + private final Long parentId; + private final String parentName; + + public SearchData(Long entityId, String entityAccountNo, String entityExternalId, String entityName, String entityType, Long parentId, String parentName) { + + this.entityId = entityId; + this.entityAccountNo = entityAccountNo; + this.entityExternalId = entityExternalId; + this.entityName = entityName; + this.entityType = entityType; + this.parentId = parentId; + this.parentName = parentName; + } + + + public Long getEntityId() { + return this.entityId; + } + + + public String getEntityAccountNo() { + return this.entityAccountNo; + } + + + public String getEntityExternalId() { + return this.entityExternalId; + } + + + public String getEntityName() { + return this.entityName; + } + + + public String getEntityType() { + return this.entityType; + } + + + + public Long getParentId() { + return this.parentId; + } + + + + public String getParentName() { + return this.parentName; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/search/service/SearchReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/search/service/SearchReadPlatformService.java new file mode 100644 index 000000000..fca16db90 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/search/service/SearchReadPlatformService.java @@ -0,0 +1,11 @@ +package org.mifosplatform.portfolio.search.service; + +import java.util.Collection; + +import org.mifosplatform.portfolio.search.data.SearchConditions; +import org.mifosplatform.portfolio.search.data.SearchData; + + +public interface SearchReadPlatformService { + Collection retriveMatchingData(SearchConditions searchConditions); +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/search/service/SearchReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/search/service/SearchReadPlatformServiceImpl.java new file mode 100644 index 000000000..34f4813b4 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/search/service/SearchReadPlatformServiceImpl.java @@ -0,0 +1,125 @@ +package org.mifosplatform.portfolio.search.service; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; + +import org.mifosplatform.infrastructure.core.domain.JdbcSupport; +import org.mifosplatform.infrastructure.core.service.TenantAwareRoutingDataSource; +import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.mifosplatform.portfolio.search.data.SearchConditions; +import org.mifosplatform.portfolio.search.data.SearchData; +import org.mifosplatform.useradministration.domain.AppUser; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Service; + +@Service +public class SearchReadPlatformServiceImpl implements SearchReadPlatformService { + + private final NamedParameterJdbcTemplate namedParameterjdbcTemplate; + private final PlatformSecurityContext context; + + @Autowired + public SearchReadPlatformServiceImpl(final PlatformSecurityContext context, final TenantAwareRoutingDataSource dataSource) { + this.context = context; + this.namedParameterjdbcTemplate = new NamedParameterJdbcTemplate(dataSource); + } + + @Override + public Collection retriveMatchingData(SearchConditions searchConditions) { + AppUser currentUser = context.authenticatedUser(); + String hierarchy = currentUser.getOffice().getHierarchy(); + + SearchMapper rm = new SearchMapper(); + + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("hierarchy", hierarchy + "%"); + params.addValue("search", searchConditions.getSearchQuery()); + params.addValue("partialSearch", "%" + searchConditions.getSearchQuery() + "%"); + + return this.namedParameterjdbcTemplate.query(rm.searchSchema(searchConditions), params, rm); + } + + private static final class SearchMapper implements RowMapper { + + public String searchSchema(SearchConditions searchConditions) { + + String union = " union all "; + String clientExactMatchSql = " (select 'CLIENT' as entityType, c.id as entityId, c.display_name as entityName, c.external_id as entityExternalId, c.account_no as entityAccountNo " + + " , c.office_id as parentId, o.name as parentName " + + " from m_client c join m_office o on o.id = c.office_id where o.hierarchy like :hierarchy and (c.account_no like :search or c.display_name like :search or c.external_id like :search)) "; + + String clientMatchSql = " (select 'CLIENT' as entityType, c.id as entityId, c.display_name as entityName, c.external_id as entityExternalId, c.account_no as entityAccountNo " + + " , c.office_id as parentId, o.name as parentName " + + " from m_client c join m_office o on o.id = c.office_id where o.hierarchy like :hierarchy and (c.account_no like :partialSearch and c.account_no not like :search) or " + + "(c.display_name like :partialSearch and c.display_name not like :search) or " + + "(c.external_id like :partialSearch and c.external_id not like :search))"; + + String loanExactMatchSql = " (select 'LOAN' as entityType, l.id as entityId, l.account_no as entityName, l.external_id as entityExternalId, l.account_no as entityAccountNo " + + " , c.id as parentId, c.display_name as parentName " + + " from m_loan l join m_client c on l.client_id = c.id join m_office o on o.id = c.office_id where o.hierarchy like :hierarchy and l.account_no like :search) "; + + String loanMatchSql = " (select 'LOAN' as entityType, l.id as entityId, l.account_no as entityName, l.external_id as entityExternalId, l.account_no as entityAccountNo " + + " , c.id as parentId, c.display_name as parentName " + + " from m_loan l join m_client c on l.client_id = c.id join m_office o on o.id = c.office_id where o.hierarchy like :hierarchy and l.account_no like :partialSearch and l.account_no not like :search) "; + + String groupExactMatchSql = " (select 'GROUP' as entityType, g.id as entityId, g.name as entityName, g.external_id as entityExternalId, NULL as entityAccountNo " + + " , g.office_id as parentId, o.name as parentName " + + " from m_group g join m_office o on o.id = g.office_id where o.hierarchy like :hierarchy and g.name like :search) "; + + String groupMatchSql = " (select 'GROUP' as entityType, g.id as entityId, g.name as entityName, g.external_id as entityExternalId, NULL as entityAccountNo " + + " , g.office_id as parentId, o.name as parentName " + + " from m_group g join m_office o on o.id = g.office_id where o.hierarchy like :hierarchy and g.name like :partialSearch and g.name not like :search) "; + + StringBuffer sql = new StringBuffer(); + + // first include all exact matches + if (searchConditions.isClientSearch()) { + sql.append(clientExactMatchSql).append(union); + } + + if (searchConditions.isLoanSeach()) { + sql.append(loanExactMatchSql).append(union); + } + + if (searchConditions.isGroupSearch()) { + sql.append(groupExactMatchSql).append(union); + } + + // include all matching records + if (searchConditions.isClientSearch()) { + sql.append(clientMatchSql).append(union); + } + + if (searchConditions.isLoanSeach()) { + sql.append(loanMatchSql).append(union); + } + + if (searchConditions.isGroupSearch()) { + sql.append(groupMatchSql).append(union); + } + + sql.replace(sql.lastIndexOf(union), sql.length(), ""); + + // remove last occurrence of "union all" string + return sql.toString(); + } + + @Override + public SearchData mapRow(ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { + final Long entityId = JdbcSupport.getLong(rs, "entityId"); + final String entityAccountNo = rs.getString("entityAccountNo"); + final String entityExternalId = rs.getString("entityExternalId"); + final String entityName = rs.getString("entityName"); + final String entityType = rs.getString("entityType"); + final Long parentId = JdbcSupport.getLong(rs, "parentId"); + final String parentName = rs.getString("parentName"); + return new SearchData(entityId, entityAccountNo, entityExternalId, entityName, entityType, parentId, parentName); + } + + } + +}