From d7d4cd4318aa1449d554455db5af135fe4dd492a Mon Sep 17 00:00:00 2001 From: Dadepo Aderemi Date: Sun, 29 Oct 2017 13:33:53 +0100 Subject: [PATCH 01/12] wip --- .../elasticsearch/ElasticSearchService.java | 9 ++- .../ElasticSearchServiceTest.java | 7 +- .../dictionary/service/SearchService.java | 17 +++++ .../dictionary/web/NameEntryService.java | 23 +++++- .../oruko/dictionary/web/rest/SearchApi.java | 73 ++++++++----------- 5 files changed, 80 insertions(+), 49 deletions(-) create mode 100644 model/src/main/java/org/oruko/dictionary/service/SearchService.java diff --git a/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchService.java b/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchService.java index 07438fde..0f323313 100644 --- a/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchService.java +++ b/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchService.java @@ -17,6 +17,7 @@ import org.elasticsearch.node.Node; import org.elasticsearch.search.SearchHit; import org.oruko.dictionary.model.NameEntry; +import org.oruko.dictionary.service.SearchService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -25,6 +26,7 @@ import org.springframework.stereotype.Service; import org.springframework.util.FileCopyUtils; +import javax.annotation.PostConstruct; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -35,7 +37,6 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.annotation.PostConstruct; import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery; @@ -45,7 +46,7 @@ * @author Dadepo Aderemi. */ @Service -public class ElasticSearchService { +public class ElasticSearchService implements SearchService { private Logger logger = LoggerFactory.getLogger(ElasticSearchService.class); @@ -86,6 +87,7 @@ public boolean isElasticSearchNodeAvailable() { * @param nameQuery the name * @return the nameEntry as a Map or null if name not found */ + @Override public Map getByName(String nameQuery) { SearchResponse searchResponse = exactSearchByName(nameQuery); @@ -103,6 +105,7 @@ public Map getByName(String nameQuery) { * @param searchTerm the search term * @return the list of entries found */ + @Override public Set> search(String searchTerm) { /** * 1. First do a exact search. If found return result. If not go to 2. @@ -181,6 +184,7 @@ public Set> search(String searchTerm) { } + @Override public Set> listByAlphabet(String alphabetQuery) { final Set> result = new LinkedHashSet<>(); @@ -202,6 +206,7 @@ public Set> listByAlphabet(String alphabetQuery) { * @param query the query * @return the list of partial matches */ + @Override public List autocomplete(String query) { final List result = new ArrayList(); diff --git a/elasticsearch/src/test/java/org/oruko/dictionary/elasticsearch/ElasticSearchServiceTest.java b/elasticsearch/src/test/java/org/oruko/dictionary/elasticsearch/ElasticSearchServiceTest.java index 1ed6dd02..aeef43da 100644 --- a/elasticsearch/src/test/java/org/oruko/dictionary/elasticsearch/ElasticSearchServiceTest.java +++ b/elasticsearch/src/test/java/org/oruko/dictionary/elasticsearch/ElasticSearchServiceTest.java @@ -5,6 +5,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.test.ElasticsearchIntegrationTest; import org.junit.*; +import org.oruko.dictionary.service.SearchService; import java.io.IOException; import java.util.Map; @@ -21,7 +22,7 @@ public class ElasticSearchServiceTest extends ElasticsearchIntegrationTest { private String dictionary = "dictionary"; private ESConfig esConfig; - ElasticSearchService elasticSearchService; + SearchService searchService; private class TestNode implements org.elasticsearch.node.Node { @@ -70,7 +71,7 @@ public void setup() throws IOException { flushAndRefresh(); - elasticSearchService = new ElasticSearchService(new TestNode(), esConfig); + searchService = new ElasticSearchService(new TestNode(), esConfig); } @Test @@ -82,7 +83,7 @@ public void testGetByName() throws IOException { flushAndRefresh(); - Map result = elasticSearchService.getByName("jamo"); + Map result = searchService.getByName("jamo"); assertEquals("jamo", result.get("name")); } diff --git a/model/src/main/java/org/oruko/dictionary/service/SearchService.java b/model/src/main/java/org/oruko/dictionary/service/SearchService.java new file mode 100644 index 00000000..3cd8bf56 --- /dev/null +++ b/model/src/main/java/org/oruko/dictionary/service/SearchService.java @@ -0,0 +1,17 @@ +package org.oruko.dictionary.service; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +public interface SearchService { + Map getByName(String nameQuery); + + Set> search(String searchTerm); + + Set> listByAlphabet(String alphabetQuery); + + List autocomplete(String query); + + Integer getSearchableNames(); +} diff --git a/webapi/src/main/java/org/oruko/dictionary/web/NameEntryService.java b/webapi/src/main/java/org/oruko/dictionary/web/NameEntryService.java index e3a96838..b355b5d8 100644 --- a/webapi/src/main/java/org/oruko/dictionary/web/NameEntryService.java +++ b/webapi/src/main/java/org/oruko/dictionary/web/NameEntryService.java @@ -8,6 +8,7 @@ import org.oruko.dictionary.model.repository.DuplicateNameEntryRepository; import org.oruko.dictionary.model.repository.NameEntryFeedbackRepository; import org.oruko.dictionary.model.repository.NameEntryRepository; +import org.oruko.dictionary.service.SearchService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -17,7 +18,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.Set; /** * The service for managing name entries @@ -25,7 +28,7 @@ * @author Dadepo Aderemi. */ @Service -public class NameEntryService { +public class NameEntryService implements SearchService { private Integer BATCH_SIZE = 50; private Integer PAGE = 0; @@ -345,5 +348,23 @@ private boolean namePresentAsVariant(String name) { }); } + @Override + public Map getByName(String nameQuery) { + return null; + } + + @Override + public Set> search(String searchTerm) { + return null; + } + @Override + public Set> listByAlphabet(String alphabetQuery) { + return null; + } + + @Override + public List autocomplete(String query) { + return null; + } } diff --git a/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java b/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java index 6e6642b2..4533628a 100644 --- a/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java +++ b/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java @@ -1,12 +1,12 @@ package org.oruko.dictionary.web.rest; -import org.oruko.dictionary.elasticsearch.ElasticSearchService; import org.oruko.dictionary.elasticsearch.IndexOperationStatus; import org.oruko.dictionary.events.EventPubService; import org.oruko.dictionary.events.NameIndexedEvent; import org.oruko.dictionary.events.NameSearchedEvent; import org.oruko.dictionary.model.NameEntry; import org.oruko.dictionary.model.State; +import org.oruko.dictionary.service.SearchService; import org.oruko.dictionary.web.NameEntryService; import org.oruko.dictionary.web.event.RecentIndexes; import org.oruko.dictionary.web.event.RecentSearches; @@ -49,8 +49,8 @@ public class SearchApi { private Logger logger = LoggerFactory.getLogger(SearchApi.class); - private NameEntryService entryService; - private ElasticSearchService elasticSearchService; + private NameEntryService nameEntryService; + private SearchService elasticSearchService; private RecentSearches recentSearches; private RecentIndexes recentIndexes; private EventPubService eventPubService; @@ -58,17 +58,17 @@ public class SearchApi { /** * Public constructor for {@link SearchApi} * - * @param entryService service layer for interacting with name entries + * @param nameEntryService service layer for interacting with name entries * @param elasticSearchService service layer for elastic search functions * @param recentSearches object holding the recent searches in memory * @param recentIndexes object holding the recent index names in memory */ @Autowired - public SearchApi(EventPubService eventPubService, NameEntryService entryService, - ElasticSearchService elasticSearchService, RecentSearches recentSearches, + public SearchApi(EventPubService eventPubService, NameEntryService nameEntryService, + SearchService elasticSearchService, RecentSearches recentSearches, RecentIndexes recentIndexes) { this.eventPubService = eventPubService; - this.entryService = entryService; + this.nameEntryService = nameEntryService; this.elasticSearchService = elasticSearchService; this.recentSearches = recentSearches; this.recentIndexes = recentIndexes; @@ -83,7 +83,7 @@ public SearchApi(EventPubService eventPubService, NameEntryService entryService, @RequestMapping(value = "/meta", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity> getMetaData() { Map metaData = new HashMap<>(); - metaData.put("totalPublishedNames", elasticSearchService.getCount()); + metaData.put("totalPublishedNames", nameEntryService.getSearchableNames()); return new ResponseEntity<>(metaData, HttpStatus.OK); } @@ -187,24 +187,18 @@ public Map allActivity() { produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity> indexEntry(@Valid NameEntry entry) { Map response = new HashMap<>(); - NameEntry nameEntry = entryService.loadName(entry.getName()); + NameEntry nameEntry = nameEntryService.loadName(entry.getName()); if (nameEntry == null) { response.put("message", "Cannot index entry. Name " + entry.getName() + " not in the database"); return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); } - IndexOperationStatus indexOperationStatus = elasticSearchService.indexName(entry); - boolean isIndexed = indexOperationStatus.getStatus(); - String message = indexOperationStatus.getMessage(); - if (isIndexed) { - publishNameIsIndexed(nameEntry); - nameEntry.setIndexed(true); - nameEntry.setState(State.PUBLISHED); - entryService.saveName(nameEntry); - response.put("message", message); - return new ResponseEntity<>(response, HttpStatus.CREATED); - } - return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); + nameEntry.setIndexed(true); + nameEntry.setState(State.PUBLISHED); + nameEntryService.saveName(nameEntry); + publishNameIsIndexed(nameEntry); + response.put("message", "Name is now searchable"); + return new ResponseEntity<>(response, HttpStatus.CREATED); } private void publishNameIsIndexed(NameEntry nameEntry) { @@ -222,28 +216,20 @@ private void publishNameIsIndexed(NameEntry nameEntry) { produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity> indexEntryByName(@PathVariable String name) { Map response = new HashMap<>(); - NameEntry nameEntry = entryService.loadName(name); + NameEntry nameEntry = nameEntryService.loadName(name); if (nameEntry == null) { // name requested to be indexed not in the database - response.put("message", - new StringBuilder(name).append(" not found in the repository so not indexed").toString()); + response.put("message", new StringBuilder(name).append(" not found in the repository so not indexed").toString()); return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); } - IndexOperationStatus indexOperationStatus = elasticSearchService.indexName(nameEntry); - boolean isIndexed = indexOperationStatus.getStatus(); - - response.put("message", indexOperationStatus.getMessage()); + publishNameIsIndexed(nameEntry); + nameEntry.setIndexed(true); + nameEntry.setState(State.PUBLISHED); + nameEntryService.saveName(nameEntry); + response.put("message", name + " has been published"); - if (isIndexed) { - publishNameIsIndexed(nameEntry); - nameEntry.setIndexed(true); - nameEntry.setState(State.PUBLISHED); - entryService.saveName(nameEntry); - return new ResponseEntity<>(response, HttpStatus.CREATED); - } - - return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); + return new ResponseEntity<>(response, HttpStatus.CREATED); } @@ -264,7 +250,7 @@ public ResponseEntity> batchIndexEntriesByName(@RequestBody List notFound = new ArrayList<>(); Arrays.stream(names).forEach(name -> { - NameEntry entry = entryService.loadName(name); + NameEntry entry = nameEntryService.loadName(name); if (entry == null) { notFound.add(name); } else { @@ -280,10 +266,11 @@ public ResponseEntity> batchIndexEntriesByName(@RequestBody IndexOperationStatus indexOperationStatus = elasticSearchService.bulkIndexName(nameEntries); updateIsIndexFlag(nameEntries, true, indexOperationStatus); + for (NameEntry nameEntry : nameEntries) { publishNameIsIndexed(nameEntry); nameEntry.setState(State.PUBLISHED); - entryService.saveName(nameEntry); + nameEntryService.saveName(nameEntry); } return returnStatusMessage(notFound, indexOperationStatus); } @@ -305,11 +292,11 @@ public ResponseEntity> deleteFromIndex(@PathVariable String Map response = new HashMap<>(); response.put("message", message); if (deleted) { - NameEntry nameEntry = entryService.loadName(name); + NameEntry nameEntry = nameEntryService.loadName(name); if (nameEntry != null) { nameEntry.setIndexed(false); nameEntry.setState(State.NEW); - entryService.saveName(nameEntry); + nameEntryService.saveName(nameEntry); } return new ResponseEntity<>(response, HttpStatus.OK); } @@ -333,7 +320,7 @@ public ResponseEntity> batchDeleteFromIndex(@RequestBody Str List notFound = new ArrayList<>(); Arrays.stream(names).forEach(name -> { - NameEntry entry = entryService.loadName(name); + NameEntry entry = nameEntryService.loadName(name); if (entry == null) { notFound.add(name); } else { @@ -360,7 +347,7 @@ private void updateIsIndexFlag(List nameEntries, boolean flag, return entry; }).collect(Collectors.toList()); - entryService.saveNames(updatedNames); + nameEntryService.saveNames(updatedNames); } } From 7a6bb549ff5bb7b5a2c215fd06e45d7685ed0c06 Mon Sep 17 00:00:00 2001 From: Dadepo Aderemi Date: Tue, 31 Oct 2017 19:44:09 +0100 Subject: [PATCH 02/12] WIP --- .../oruko/dictionary/util/DataImporter.java | 9 - ...ervice.java => ElasticSearchServicex.java} | 218 +++++++++--------- .../ElasticSearchServiceTest.java | 143 ++++++------ .../service}/IndexOperationStatus.java | 2 +- .../dictionary/service/SearchService.java | 35 ++- .../dictionary/web/NameEntryService.java | 50 +++- .../web/event/NameDeletedEventHandler.java | 10 +- .../oruko/dictionary/web/rest/SearchApi.java | 40 ++-- .../dictionary/web/rest/SearchApiTest.java | 8 +- 9 files changed, 285 insertions(+), 230 deletions(-) rename elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/{ElasticSearchService.java => ElasticSearchServicex.java} (70%) rename {elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch => model/src/main/java/org/oruko/dictionary/service}/IndexOperationStatus.java (91%) diff --git a/bootstrap/src/main/java/org/oruko/dictionary/util/DataImporter.java b/bootstrap/src/main/java/org/oruko/dictionary/util/DataImporter.java index 7abbe12d..a0f49ee4 100644 --- a/bootstrap/src/main/java/org/oruko/dictionary/util/DataImporter.java +++ b/bootstrap/src/main/java/org/oruko/dictionary/util/DataImporter.java @@ -1,6 +1,5 @@ package org.oruko.dictionary.util; -import org.oruko.dictionary.elasticsearch.ElasticSearchService; import org.oruko.dictionary.model.GeoLocation; import org.oruko.dictionary.model.NameEntry; import org.oruko.dictionary.model.State; @@ -30,9 +29,6 @@ public class DataImporter { @Autowired private GeoLocationRepository geoLocationRepository; - @Autowired - private ElasticSearchService elasticSearchService; - @Autowired private NameEntryRepository nameEntryRepository; @@ -49,7 +45,6 @@ public void initializeData() { */ if (host.equalsIgnoreCase("localhost")) { List nameEntries = initializeDb(); - initializeElastic(nameEntries); } } @@ -186,10 +181,6 @@ private List initializeDb() { ade0, ade1, ade2, ade3, ade4, omowumi, omolabi); } - private void initializeElastic(List nameEntries) { - elasticSearchService.bulkIndexName(nameEntries); - } - private void initGeoLocation() { // North-West Yoruba (NWY): Abẹokuta, Ibadan, Ọyọ, Ogun and Lagos (Eko) areas // Central Yoruba (CY): Igbomina, Yagba, Ilésà, Ifẹ, Ekiti, Akurẹ, Ẹfọn, and Ijẹbu areas. diff --git a/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchService.java b/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchServicex.java similarity index 70% rename from elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchService.java rename to elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchServicex.java index 0f323313..625d17f2 100644 --- a/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchService.java +++ b/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchServicex.java @@ -1,14 +1,9 @@ package org.oruko.dictionary.elasticsearch; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.elasticsearch.action.admin.indices.create.CreateIndexResponse; -import org.elasticsearch.action.bulk.BulkRequestBuilder; -import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.count.CountResponse; -import org.elasticsearch.action.delete.DeleteRequestBuilder; import org.elasticsearch.action.delete.DeleteResponse; -import org.elasticsearch.action.index.IndexRequestBuilder; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.client.Client; import org.elasticsearch.index.query.FilterBuilders; @@ -17,6 +12,7 @@ import org.elasticsearch.node.Node; import org.elasticsearch.search.SearchHit; import org.oruko.dictionary.model.NameEntry; +import org.oruko.dictionary.service.IndexOperationStatus; import org.oruko.dictionary.service.SearchService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,7 +31,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; import java.util.stream.Stream; import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery; @@ -46,9 +41,9 @@ * @author Dadepo Aderemi. */ @Service -public class ElasticSearchService implements SearchService { +public class ElasticSearchServicex implements SearchService { - private Logger logger = LoggerFactory.getLogger(ElasticSearchService.class); + private Logger logger = LoggerFactory.getLogger(ElasticSearchServicex.class); private Node node; private Client client; @@ -57,13 +52,13 @@ public class ElasticSearchService implements SearchService { private ObjectMapper mapper = new ObjectMapper(); /** - * Public constructor for {@link ElasticSearchService} + * Public constructor for {@link ElasticSearchServicex} * * @param node the elastic search Node * @param esConfig the elastic search config */ @Autowired - public ElasticSearchService(Node node, ESConfig esConfig) { + public ElasticSearchServicex(Node node, ESConfig esConfig) { this.node = node; this.client = node.client(); this.esConfig = esConfig; @@ -74,11 +69,7 @@ public void setResourceLoader(ResourceLoader loader) { this.resourceLoader = loader; } - ElasticSearchService() { - } - - public boolean isElasticSearchNodeAvailable() { - return !node.isClosed(); + ElasticSearchServicex() { } /** @@ -88,15 +79,16 @@ public boolean isElasticSearchNodeAvailable() { * @return the nameEntry as a Map or null if name not found */ @Override - public Map getByName(String nameQuery) { - SearchResponse searchResponse = exactSearchByName(nameQuery); - - SearchHit[] hits = searchResponse.getHits().getHits(); - if (hits.length == 1) { - return hits[0].getSource(); - } else { - return null; - } + public Map getByName(String nameQuery) { + return null; +// SearchResponse searchResponse = exactSearchByName(nameQuery); +// +// SearchHit[] hits = searchResponse.getHits().getHits(); +// if (hits.length == 1) { +// return hits[0].getSource(); +// } else { +// return null; +// } } /** @@ -219,6 +211,11 @@ public List autocomplete(String query) { return result; } + @Override + public Integer getSearchableNames() { + return null; + } + /** * Add a {@link org.oruko.dictionary.model.NameEntry} into ElasticSearch index @@ -227,61 +224,61 @@ public List autocomplete(String query) { * @return returns true | false depending on if the indexing operation was successful. */ public IndexOperationStatus indexName(NameEntry entry) { - - if (!isElasticSearchNodeAvailable()) { - return new IndexOperationStatus(false, - "Index attempt unsuccessful. You do not have an elasticsearch node running"); - } - - try { - String entryAsJson = mapper.writeValueAsString(entry); - String name = entry.getName(); - client.prepareIndex(esConfig.getIndexName(), esConfig.getDocumentType(), name.toLowerCase()) - .setSource(entryAsJson) - .execute() - .actionGet(); - - return new IndexOperationStatus(true, name + " indexed successfully"); - } catch (JsonProcessingException e) { - logger.info("Failed to parse NameEntry into Json", e); - return new IndexOperationStatus(true, "Failed to parse NameEntry into Json"); - } + return null; +// if (!isElasticSearchNodeAvailable()) { +// return new IndexOperationStatus(false, +// "Index attempt unsuccessful. You do not have an elasticsearch node running"); +// } +// +// try { +// String entryAsJson = mapper.writeValueAsString(entry); +// String name = entry.getName(); +// client.prepareIndex(esConfig.getIndexName(), esConfig.getDocumentType(), name.toLowerCase()) +// .setSource(entryAsJson) +// .execute() +// .actionGet(); +// +// return new IndexOperationStatus(true, name + " indexed successfully"); +// } catch (JsonProcessingException e) { +// logger.info("Failed to parse NameEntry into Json", e); +// return new IndexOperationStatus(true, "Failed to parse NameEntry into Json"); +// } } public IndexOperationStatus bulkIndexName(List entries) { - - if (entries.size() == 0) { - return new IndexOperationStatus(false, "Cannot index an empty list"); - } - - if (!isElasticSearchNodeAvailable()) { - return new IndexOperationStatus(false, - "Index attempt unsuccessful. You do not have an elasticsearch node running"); - } - - BulkRequestBuilder bulkRequest = client.prepareBulk(); - entries.forEach(entry -> { - try { - String entryAsJson = mapper.writeValueAsString(entry); - String name = entry.getName(); - IndexRequestBuilder request = client.prepareIndex(esConfig.getIndexName(), - esConfig.getDocumentType(), - name.toLowerCase()) - .setSource(entryAsJson); - bulkRequest.add(request); - } catch (JsonProcessingException e) { - logger.debug("Error while building bulk indexing operation", e); - } - }); - - BulkResponse bulkResponse = bulkRequest.execute().actionGet(); - if (bulkResponse.hasFailures()) { - return new IndexOperationStatus(false, bulkResponse.buildFailureMessage()); - } - - return new IndexOperationStatus(true, "Bulk indexing successfully. Indexed the following names " - + String.join(",", entries.stream().map(entry -> entry.getName()).collect(Collectors.toList()))); + return null; +// if (entries.size() == 0) { +// return new IndexOperationStatus(false, "Cannot index an empty list"); +// } +// +// if (!isElasticSearchNodeAvailable()) { +// return new IndexOperationStatus(false, +// "Index attempt unsuccessful. You do not have an elasticsearch node running"); +// } +// +// BulkRequestBuilder bulkRequest = client.prepareBulk(); +// entries.forEach(entry -> { +// try { +// String entryAsJson = mapper.writeValueAsString(entry); +// String name = entry.getName(); +// IndexRequestBuilder request = client.prepareIndex(esConfig.getIndexName(), +// esConfig.getDocumentType(), +// name.toLowerCase()) +// .setSource(entryAsJson); +// bulkRequest.add(request); +// } catch (JsonProcessingException e) { +// logger.debug("Error while building bulk indexing operation", e); +// } +// }); +// +// BulkResponse bulkResponse = bulkRequest.execute().actionGet(); +// if (bulkResponse.hasFailures()) { +// return new IndexOperationStatus(false, bulkResponse.buildFailureMessage()); +// } +// +// return new IndexOperationStatus(true, "Bulk indexing successfully. Indexed the following names " +// + String.join(",", entries.stream().map(entry -> entry.getName()).collect(Collectors.toList()))); } /** @@ -291,44 +288,45 @@ public IndexOperationStatus bulkIndexName(List entries) { * @return true | false depending on if deleting operation was successful */ public IndexOperationStatus deleteFromIndex(String name) { - - if (!isElasticSearchNodeAvailable()) { - - return new IndexOperationStatus(false, - "Delete unsuccessful. You do not have an elasticsearch node running"); - } - - DeleteResponse response = deleteName(name); - return new IndexOperationStatus(response.isFound(), name + " deleted from index"); + return null; +// if (!isElasticSearchNodeAvailable()) { +// +// return new IndexOperationStatus(false, +// "Delete unsuccessful. You do not have an elasticsearch node running"); +// } +// +// DeleteResponse response = deleteName(name); +// return new IndexOperationStatus(response.isFound(), name + " deleted from index"); } public IndexOperationStatus bulkDeleteFromIndex(List entries) { - if (entries.size() == 0) { - return new IndexOperationStatus(false, "Cannot index an empty list"); - } - - if (!isElasticSearchNodeAvailable()) { - return new IndexOperationStatus(false, - "Delete unsuccessful. You do not have an elasticsearch node running"); - } - - BulkRequestBuilder bulkRequest = client.prepareBulk(); - - entries.forEach(entry -> { - DeleteRequestBuilder request = client.prepareDelete(esConfig.getIndexName(), - esConfig.getDocumentType(), - entry.toLowerCase()); - bulkRequest.add(request); - }); - - BulkResponse bulkResponse = bulkRequest.execute().actionGet(); - if (bulkResponse.hasFailures()) { - return new IndexOperationStatus(false, bulkResponse.buildFailureMessage()); - } - - return new IndexOperationStatus(true, "Bulk deleting successfully. " - + "Removed the following names from search index " - + String.join(",", entries)); + return null; +// if (entries.size() == 0) { +// return new IndexOperationStatus(false, "Cannot index an empty list"); +// } +// +// if (!isElasticSearchNodeAvailable()) { +// return new IndexOperationStatus(false, +// "Delete unsuccessful. You do not have an elasticsearch node running"); +// } +// +// BulkRequestBuilder bulkRequest = client.prepareBulk(); +// +// entries.forEach(entry -> { +// DeleteRequestBuilder request = client.prepareDelete(esConfig.getIndexName(), +// esConfig.getDocumentType(), +// entry.toLowerCase()); +// bulkRequest.add(request); +// }); +// +// BulkResponse bulkResponse = bulkRequest.execute().actionGet(); +// if (bulkResponse.hasFailures()) { +// return new IndexOperationStatus(false, bulkResponse.buildFailureMessage()); +// } +// +// return new IndexOperationStatus(true, "Bulk deleting successfully. " +// + "Removed the following names from search index " +// + String.join(",", entries)); } diff --git a/elasticsearch/src/test/java/org/oruko/dictionary/elasticsearch/ElasticSearchServiceTest.java b/elasticsearch/src/test/java/org/oruko/dictionary/elasticsearch/ElasticSearchServiceTest.java index aeef43da..620a6cb9 100644 --- a/elasticsearch/src/test/java/org/oruko/dictionary/elasticsearch/ElasticSearchServiceTest.java +++ b/elasticsearch/src/test/java/org/oruko/dictionary/elasticsearch/ElasticSearchServiceTest.java @@ -1,90 +1,81 @@ package org.oruko.dictionary.elasticsearch; -import org.elasticsearch.client.Client; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.test.ElasticsearchIntegrationTest; -import org.junit.*; -import org.oruko.dictionary.service.SearchService; -import java.io.IOException; -import java.util.Map; - -import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope; /** - * Integration test for {@link ElasticSearchService} + * * Created by Dadepo Aderemi. */ @ClusterScope(scope = ElasticsearchIntegrationTest.Scope.TEST) public class ElasticSearchServiceTest extends ElasticsearchIntegrationTest { - private String dictionary = "dictionary"; - private ESConfig esConfig; - SearchService searchService; - - private class TestNode implements org.elasticsearch.node.Node { - - @Override - public Settings settings() { - return null; - } - - @Override - public Client client() { - return org.elasticsearch.test.ElasticsearchIntegrationTest.client(); - } - - @Override - public org.elasticsearch.node.Node start() { - return null; - } - - @Override - public org.elasticsearch.node.Node stop() { - return null; - } - - @Override - public void close() { - - } - - @Override - public boolean isClosed() { - return false; - } - } - - @Before - public void setup() throws IOException { - - esConfig = new ESConfig(); - esConfig.setDocumentType("nameentry"); - esConfig.setIndexName("dictionary"); - esConfig.setClusterName("yoruba_name_dictionary"); - esConfig.setHostName("localhost"); - esConfig.setPort(9300); - - createIndex(dictionary); - - flushAndRefresh(); - - searchService = new ElasticSearchService(new TestNode(), esConfig); - } - - @Test - public void testGetByName() throws IOException { - XContentBuilder lagbaja = jsonBuilder().startObject() - .field("name", "jamo") - .endObject(); - index(esConfig.getIndexName(), esConfig.getDocumentType(), lagbaja); - - flushAndRefresh(); - - Map result = searchService.getByName("jamo"); - assertEquals("jamo", result.get("name")); - } +// private String dictionary = "dictionary"; +// private ESConfig esConfig; +// SearchService searchService; +// +// private class TestNode implements org.elasticsearch.node.Node { +// +// @Override +// public Settings settings() { +// return null; +// } +// +// @Override +// public Client client() { +// return org.elasticsearch.test.ElasticsearchIntegrationTest.client(); +// } +// +// @Override +// public org.elasticsearch.node.Node start() { +// return null; +// } +// +// @Override +// public org.elasticsearch.node.Node stop() { +// return null; +// } +// +// @Override +// public void close() { +// +// } +// +// @Override +// public boolean isClosed() { +// return false; +// } +// } +// +// @Before +// public void setup() throws IOException { +// +// esConfig = new ESConfig(); +// esConfig.setDocumentType("nameentry"); +// esConfig.setIndexName("dictionary"); +// esConfig.setClusterName("yoruba_name_dictionary"); +// esConfig.setHostName("localhost"); +// esConfig.setPort(9300); +// +// createIndex(dictionary); +// +// flushAndRefresh(); +// +// searchService = new ElasticSearchService(new TestNode(), esConfig); +// } +// +// @Test +// public void testGetByName() throws IOException { +// XContentBuilder lagbaja = jsonBuilder().startObject() +// .field("name", "jamo") +// .endObject(); +// index(esConfig.getIndexName(), esConfig.getDocumentType(), lagbaja); +// +// flushAndRefresh(); +// +// Map result = searchService.getByName("jamo"); +// assertEquals("jamo", result.get("name")); +// } } diff --git a/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/IndexOperationStatus.java b/model/src/main/java/org/oruko/dictionary/service/IndexOperationStatus.java similarity index 91% rename from elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/IndexOperationStatus.java rename to model/src/main/java/org/oruko/dictionary/service/IndexOperationStatus.java index b340c724..9489da6c 100644 --- a/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/IndexOperationStatus.java +++ b/model/src/main/java/org/oruko/dictionary/service/IndexOperationStatus.java @@ -1,4 +1,4 @@ -package org.oruko.dictionary.elasticsearch; +package org.oruko.dictionary.service; /** * For communicating the status of operation on the index diff --git a/model/src/main/java/org/oruko/dictionary/service/SearchService.java b/model/src/main/java/org/oruko/dictionary/service/SearchService.java index 3cd8bf56..43886726 100644 --- a/model/src/main/java/org/oruko/dictionary/service/SearchService.java +++ b/model/src/main/java/org/oruko/dictionary/service/SearchService.java @@ -1,17 +1,50 @@ package org.oruko.dictionary.service; +import org.oruko.dictionary.model.NameEntry; + import java.util.List; import java.util.Map; import java.util.Set; public interface SearchService { - Map getByName(String nameQuery); + /** + * For getting an entry from the search index by name + * + * @param nameQuery the name + * @return the nameEntry as a Map or null if name not found + */ + Map getByName(String nameQuery); + /** + * For searching the name entries for a name + * + * + * + * @param searchTerm the search term + * @return the list of entries found + */ Set> search(String searchTerm); + /** + * Return all the names which starts with the given alphabet + * + * @param alphabetQuery the given alphabet + * + * @return the list of names that starts with the given alphabet + */ Set> listByAlphabet(String alphabetQuery); + /** + * For getting the list of partial matches for autocomplete + * + * @param query the query + * @return the list of partial matches + */ List autocomplete(String query); Integer getSearchableNames(); + + IndexOperationStatus bulkIndexName(List entries); + IndexOperationStatus deleteFromIndex(String name); + IndexOperationStatus bulkDeleteFromIndex(List name); } diff --git a/webapi/src/main/java/org/oruko/dictionary/web/NameEntryService.java b/webapi/src/main/java/org/oruko/dictionary/web/NameEntryService.java index b355b5d8..2fc253e4 100644 --- a/webapi/src/main/java/org/oruko/dictionary/web/NameEntryService.java +++ b/webapi/src/main/java/org/oruko/dictionary/web/NameEntryService.java @@ -8,6 +8,7 @@ import org.oruko.dictionary.model.repository.DuplicateNameEntryRepository; import org.oruko.dictionary.model.repository.NameEntryFeedbackRepository; import org.oruko.dictionary.model.repository.NameEntryRepository; +import org.oruko.dictionary.service.IndexOperationStatus; import org.oruko.dictionary.service.SearchService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -17,8 +18,10 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -349,22 +352,65 @@ private boolean namePresentAsVariant(String name) { } @Override - public Map getByName(String nameQuery) { - return null; + public Map getByName(String nameQuery) { + // TODO Confirm that the accents are respected + NameEntry foundName = nameEntryRepository.findByName(nameQuery); + if (Objects.isNull(foundName)) { + return null; + } + return new HashMap() {{ + put(nameQuery, foundName); + }}; } @Override public Set> search(String searchTerm) { + /** + * The following approach should be taken: + * + * 1. First do a exact search. If found return result. If not go to 2. + * 2. Do a search with ascii-folding. If found return result, if not go to 3 + * 3. Do a prefix search. If found, return result, if not go to 4 + * 4. Do a search based on partial match. Irrespective of outcome, proceed to 4 + * 4a - Using nGram? + * 4b - ? + * 5. Do a full text search against other variants. Irrespective of outcome, proceed to 6 + * 6. Do a full text search against meaning. Irrespective of outcome, proceed to 7 + * 7. Do a full text search against extendedMeaning + */ return null; } @Override public Set> listByAlphabet(String alphabetQuery) { + // TODO fetch by alphabet. Respect the accents return null; } @Override public List autocomplete(String query) { + // TODO implement, disregarding the accents + return null; + } + + @Override + public Integer getSearchableNames() { + // TODO implement + return null; + } + + @Override + public IndexOperationStatus bulkIndexName(List entries) { + return null; + } + + public IndexOperationStatus deleteFromIndex(String name) { + // TODO implement + return null; + } + + @Override + public IndexOperationStatus bulkDeleteFromIndex(List name) { return null; } } diff --git a/webapi/src/main/java/org/oruko/dictionary/web/event/NameDeletedEventHandler.java b/webapi/src/main/java/org/oruko/dictionary/web/event/NameDeletedEventHandler.java index c079df12..d70a6f5b 100644 --- a/webapi/src/main/java/org/oruko/dictionary/web/event/NameDeletedEventHandler.java +++ b/webapi/src/main/java/org/oruko/dictionary/web/event/NameDeletedEventHandler.java @@ -2,20 +2,22 @@ import com.google.common.eventbus.AllowConcurrentEvents; import com.google.common.eventbus.Subscribe; -import org.oruko.dictionary.elasticsearch.ElasticSearchService; import org.oruko.dictionary.events.NameDeletedEvent; +import org.oruko.dictionary.service.SearchService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; /** - * Handler for {@link org.oruko.dictionary.events.NameDeletedEvent} + * Handler for {@link NameDeletedEvent} * @author Dadepo Aderemi. */ @Component public class NameDeletedEventHandler { + @Qualifier("nameEntryService") @Autowired - ElasticSearchService elasticSearchService; + SearchService nameSearchService; @Autowired RecentIndexes recentIndexes; @Autowired @@ -26,7 +28,7 @@ public class NameDeletedEventHandler { public void listen(NameDeletedEvent event) { // Handle when a name is deleted try { - elasticSearchService.deleteFromIndex(event.getName()); + nameSearchService.deleteFromIndex(event.getName()); recentIndexes.remove(event.getName()); recentSearches.remove(event.getName()); } catch (Exception e) { diff --git a/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java b/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java index 4533628a..5033ae3f 100644 --- a/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java +++ b/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java @@ -1,12 +1,11 @@ package org.oruko.dictionary.web.rest; -import org.oruko.dictionary.elasticsearch.IndexOperationStatus; import org.oruko.dictionary.events.EventPubService; import org.oruko.dictionary.events.NameIndexedEvent; import org.oruko.dictionary.events.NameSearchedEvent; import org.oruko.dictionary.model.NameEntry; import org.oruko.dictionary.model.State; -import org.oruko.dictionary.service.SearchService; +import org.oruko.dictionary.service.IndexOperationStatus; import org.oruko.dictionary.web.NameEntryService; import org.oruko.dictionary.web.event.RecentIndexes; import org.oruko.dictionary.web.event.RecentSearches; @@ -50,7 +49,6 @@ public class SearchApi { private Logger logger = LoggerFactory.getLogger(SearchApi.class); private NameEntryService nameEntryService; - private SearchService elasticSearchService; private RecentSearches recentSearches; private RecentIndexes recentIndexes; private EventPubService eventPubService; @@ -59,17 +57,14 @@ public class SearchApi { * Public constructor for {@link SearchApi} * * @param nameEntryService service layer for interacting with name entries - * @param elasticSearchService service layer for elastic search functions * @param recentSearches object holding the recent searches in memory * @param recentIndexes object holding the recent index names in memory */ @Autowired public SearchApi(EventPubService eventPubService, NameEntryService nameEntryService, - SearchService elasticSearchService, RecentSearches recentSearches, - RecentIndexes recentIndexes) { + RecentSearches recentSearches, RecentIndexes recentIndexes) { this.eventPubService = eventPubService; this.nameEntryService = nameEntryService; - this.elasticSearchService = elasticSearchService; this.recentSearches = recentSearches; this.recentIndexes = recentIndexes; } @@ -97,12 +92,11 @@ public ResponseEntity> getMetaData() { public Set> search(@RequestParam(value = "q", required = true) String searchTerm, HttpServletRequest request) { - Set> name = elasticSearchService.search(searchTerm); + Set> name = nameEntryService.search(searchTerm); if (name != null && name.size() == 1 && name.stream().allMatch(result -> result.get("name").equals(searchTerm))) { - eventPubService.publish(new NameSearchedEvent(searchTerm, - request.getRemoteAddr().toString())); + eventPubService.publish(new NameSearchedEvent(searchTerm, request.getRemoteAddr())); } return name; } @@ -111,11 +105,11 @@ public Set> search(@RequestParam(value = "q", required = tru produces = MediaType.APPLICATION_JSON_VALUE) public List getAutocomplete(@RequestParam(value = "q") Optional searchQuery) { if (!searchQuery.isPresent()) { - return Collections.EMPTY_LIST; + return Collections.emptyList(); } String query = searchQuery.get(); - return elasticSearchService.autocomplete(query); + return nameEntryService.autocomplete(query); } @@ -123,21 +117,19 @@ public List getAutocomplete(@RequestParam(value = "q") Optional produces = MediaType.APPLICATION_JSON_VALUE) public Set> getByAlphabet(@PathVariable Optional alphabet) { if (!alphabet.isPresent()) { - return Collections.EMPTY_SET; + return Collections.emptySet(); } - - - return elasticSearchService.listByAlphabet(alphabet.get()); + return nameEntryService.listByAlphabet(alphabet.get()); } @RequestMapping(value = "/{searchTerm}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) - public Map findByName(@PathVariable String searchTerm, HttpServletRequest request) { + public Map findByName(@PathVariable String searchTerm, HttpServletRequest request) { - Map name = elasticSearchService.getByName(searchTerm); + Map name = nameEntryService.getByName(searchTerm); if (name != null) { - eventPubService.publish(new NameSearchedEvent(searchTerm, request.getRemoteAddr().toString())); + eventPubService.publish(new NameSearchedEvent(searchTerm, request.getRemoteAddr())); } return name; } @@ -264,7 +256,7 @@ public ResponseEntity> batchIndexEntriesByName(@RequestBody return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); } - IndexOperationStatus indexOperationStatus = elasticSearchService.bulkIndexName(nameEntries); + IndexOperationStatus indexOperationStatus = nameEntryService.bulkIndexName(nameEntries); updateIsIndexFlag(nameEntries, true, indexOperationStatus); for (NameEntry nameEntry : nameEntries) { @@ -272,7 +264,9 @@ public ResponseEntity> batchIndexEntriesByName(@RequestBody nameEntry.setState(State.PUBLISHED); nameEntryService.saveName(nameEntry); } - return returnStatusMessage(notFound, indexOperationStatus); + + // TODO re-implement to return the message + return new ResponseEntity<>(HttpStatus.CREATED); } @@ -286,7 +280,7 @@ public ResponseEntity> batchIndexEntriesByName(@RequestBody consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity> deleteFromIndex(@PathVariable String name) { - IndexOperationStatus indexOperationStatus = elasticSearchService.deleteFromIndex(name); + IndexOperationStatus indexOperationStatus = nameEntryService.deleteFromIndex(name); boolean deleted = indexOperationStatus.getStatus(); String message = indexOperationStatus.getMessage(); Map response = new HashMap<>(); @@ -334,7 +328,7 @@ public ResponseEntity> batchDeleteFromIndex(@RequestBody Str return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); } - IndexOperationStatus indexOperationStatus = elasticSearchService.bulkDeleteFromIndex(found); + IndexOperationStatus indexOperationStatus = nameEntryService.bulkDeleteFromIndex(found); updateIsIndexFlag(nameEntries, false, indexOperationStatus); return returnStatusMessage(notFound, indexOperationStatus); } diff --git a/webapi/src/test/java/org/oruko/dictionary/web/rest/SearchApiTest.java b/webapi/src/test/java/org/oruko/dictionary/web/rest/SearchApiTest.java index 58cfb540..70b3a9bc 100644 --- a/webapi/src/test/java/org/oruko/dictionary/web/rest/SearchApiTest.java +++ b/webapi/src/test/java/org/oruko/dictionary/web/rest/SearchApiTest.java @@ -4,8 +4,8 @@ import org.junit.runner.RunWith; import org.mockito.*; import org.mockito.runners.MockitoJUnitRunner; -import org.oruko.dictionary.elasticsearch.ElasticSearchService; import org.oruko.dictionary.events.EventPubService; +import org.oruko.dictionary.service.SearchService; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -27,7 +27,7 @@ public class SearchApiTest extends AbstractApiTest { MockMvc mockMvc; @Mock - ElasticSearchService searchService; + SearchService searchService; @Mock EventPubService eventPubService; @@ -40,12 +40,12 @@ public void setUp() { @Test public void testMetadata() throws Exception { - when(searchService.getCount()).thenReturn(3L); + when(searchService.getSearchableNames()).thenReturn(3); mockMvc.perform(get("/v1/search/meta")) .andExpect(jsonPath("$.totalPublishedNames", is(3))) .andExpect(status().isOk()); - verify(searchService).getCount(); + verify(searchService).getSearchableNames(); } @Test From 5eb25bd02156f2f167954daadc7c7b75db8fa6bc Mon Sep 17 00:00:00 2001 From: Dadepo Aderemi Date: Tue, 7 Nov 2017 23:13:50 +0100 Subject: [PATCH 03/12] wip --- .../oruko/dictionary/util/DataImporter.java | 13 ++- .../elasticsearch/ElasticSearchServicex.java | 6 +- .../dictionary/model/AbstractNameEntry.java | 5 +- .../model/repository/NameEntryRepository.java | 7 +- .../dictionary/service/JpaSearchService.java | 76 ++++++++++++++++++ .../dictionary/service/SearchService.java | 6 +- .../dictionary/web/NameEntryService.java | 71 +---------------- .../web/event/NameDeletedEventHandler.java | 2 +- .../oruko/dictionary/web/rest/SearchApi.java | 30 ++++--- .../dictionary/web/rest/SearchApiTest.java | 18 +++-- .../dictionary/DictionaryApplication.java | 2 +- .../oruko/dictionary/website/ApiService.java | 10 +-- .../website/SearchResultController.java | 3 +- .../src/main/resources/application.properties | 2 +- .../src/main/resources/website/contactus.hbs | 79 ++++++------------- .../main/resources/website/singleresult.hbs | 4 +- 16 files changed, 170 insertions(+), 164 deletions(-) create mode 100644 model/src/main/java/org/oruko/dictionary/service/JpaSearchService.java diff --git a/bootstrap/src/main/java/org/oruko/dictionary/util/DataImporter.java b/bootstrap/src/main/java/org/oruko/dictionary/util/DataImporter.java index a0f49ee4..f9cc6b22 100644 --- a/bootstrap/src/main/java/org/oruko/dictionary/util/DataImporter.java +++ b/bootstrap/src/main/java/org/oruko/dictionary/util/DataImporter.java @@ -106,7 +106,17 @@ private List initializeDb() { bolanle.setExtendedMeaning("This is extended dummy meaning for Bọ́lánlé"); bolanle.setGeoLocation(Arrays.asList(new GeoLocation("IBADAN", "NWY"))); bolanle.setEtymology(etymology); - bolanle.setState(State.NEW); + bolanle.setIndexed(true); + bolanle.setState(State.PUBLISHED); + + + NameEntry bimpe = new NameEntry("Bimpe"); + bimpe.setMeaning("This is dummy meaning for Bimpe"); + bimpe.setExtendedMeaning("This is extended dummy meaning for Bimpe"); + bimpe.setGeoLocation(Arrays.asList(new GeoLocation("IBADAN", "NWY"))); + bimpe.setEtymology(etymology); + bolanle.setIndexed(true); + bimpe.setState(State.PUBLISHED); NameEntry ade0 = new NameEntry("Ade"); ade0.setMeaning("This is dummy meaning for ade"); @@ -169,6 +179,7 @@ private List initializeDb() { nameEntryRepository.save(tola); nameEntryRepository.save(dadepo); nameEntryRepository.save(bolanle); + nameEntryRepository.save(bimpe); nameEntryRepository.save(ade0); nameEntryRepository.save(ade1); nameEntryRepository.save(ade2); diff --git a/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchServicex.java b/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchServicex.java index 625d17f2..025024e1 100644 --- a/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchServicex.java +++ b/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchServicex.java @@ -79,7 +79,7 @@ public void setResourceLoader(ResourceLoader loader) { * @return the nameEntry as a Map or null if name not found */ @Override - public Map getByName(String nameQuery) { + public NameEntry getByName(String nameQuery) { return null; // SearchResponse searchResponse = exactSearchByName(nameQuery); // @@ -177,7 +177,7 @@ public Set> search(String searchTerm) { @Override - public Set> listByAlphabet(String alphabetQuery) { + public Set listByAlphabet(String alphabetQuery) { final Set> result = new LinkedHashSet<>(); final SearchResponse searchResponse = prefixFilterSearch(alphabetQuery, true); @@ -189,7 +189,7 @@ public Set> listByAlphabet(String alphabetQuery) { result.add(hit.getSource()); }); - return result; + return null; } /** diff --git a/model/src/main/java/org/oruko/dictionary/model/AbstractNameEntry.java b/model/src/main/java/org/oruko/dictionary/model/AbstractNameEntry.java index 005c5324..e519cad5 100644 --- a/model/src/main/java/org/oruko/dictionary/model/AbstractNameEntry.java +++ b/model/src/main/java/org/oruko/dictionary/model/AbstractNameEntry.java @@ -6,8 +6,6 @@ import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import org.oruko.dictionary.model.repository.Etymology; -import java.time.LocalDateTime; -import java.util.List; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.EnumType; @@ -18,6 +16,8 @@ import javax.persistence.JoinColumn; import javax.persistence.ManyToMany; import javax.persistence.MappedSuperclass; +import java.time.LocalDateTime; +import java.util.List; /** * 1. Name @@ -257,6 +257,7 @@ public Boolean getIndexed() { return isIndexed; } + // REMOVE AND USE PUBLISHED STATE public void setIndexed(Boolean published) { this.isIndexed = published; } diff --git a/model/src/main/java/org/oruko/dictionary/model/repository/NameEntryRepository.java b/model/src/main/java/org/oruko/dictionary/model/repository/NameEntryRepository.java index a1aa056f..7a1b83c7 100644 --- a/model/src/main/java/org/oruko/dictionary/model/repository/NameEntryRepository.java +++ b/model/src/main/java/org/oruko/dictionary/model/repository/NameEntryRepository.java @@ -5,8 +5,9 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -import java.util.List; import javax.transaction.Transactional; +import java.util.List; +import java.util.Set; /** * Created by dadepo on 2/4/15. @@ -36,4 +37,8 @@ public interface NameEntryRepository extends JpaRepository { * @return list of {@link NameEntry} */ List findByState(State state); + + // Search related repository access + Set findByNameStartingWithAndState(String alphabet, State state); + NameEntry findByNameAndState(String alphabet, State state); } diff --git a/model/src/main/java/org/oruko/dictionary/service/JpaSearchService.java b/model/src/main/java/org/oruko/dictionary/service/JpaSearchService.java new file mode 100644 index 00000000..d3e0911b --- /dev/null +++ b/model/src/main/java/org/oruko/dictionary/service/JpaSearchService.java @@ -0,0 +1,76 @@ +package org.oruko.dictionary.service; + +import org.oruko.dictionary.model.NameEntry; +import org.oruko.dictionary.model.State; +import org.oruko.dictionary.model.repository.NameEntryRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Service +public class JpaSearchService implements SearchService { + private NameEntryRepository nameEntryRepository; + + @Autowired + public JpaSearchService(NameEntryRepository nameEntryRepository) { + this.nameEntryRepository = nameEntryRepository; + } + + @Override + public NameEntry getByName(String nameQuery) { + return nameEntryRepository.findByNameAndState(nameQuery, State.PUBLISHED); + } + + @Override + public Set> search(String searchTerm) { + /** + * The following approach should be taken: + * + * 1. First do a exact search. If found return result. If not go to 2. + * 2. Do a search with ascii-folding. If found return result, if not go to 3 + * 3. Do a prefix search. If found, return result, if not go to 4 + * 4. Do a search based on partial match. Irrespective of outcome, proceed to 4 + * 4a - Using nGram? + * 4b - ? + * 5. Do a full text search against other variants. Irrespective of outcome, proceed to 6 + * 6. Do a full text search against meaning. Irrespective of outcome, proceed to 7 + * 7. Do a full text search against extendedMeaning + */ + return null; + } + + @Override + public Set listByAlphabet(String alphabetQuery) { + return nameEntryRepository.findByNameStartingWithAndState(alphabetQuery, State.PUBLISHED); + } + + @Override + public List autocomplete(String query) { + // TODO implement, disregarding the accents + return null; + } + + @Override + public Integer getSearchableNames() { + // TODO implement + return null; + } + + @Override + public IndexOperationStatus bulkIndexName(List entries) { + return null; + } + + public IndexOperationStatus deleteFromIndex(String name) { + // TODO implement + return null; + } + + @Override + public IndexOperationStatus bulkDeleteFromIndex(List name) { + return null; + } +} diff --git a/model/src/main/java/org/oruko/dictionary/service/SearchService.java b/model/src/main/java/org/oruko/dictionary/service/SearchService.java index 43886726..0a6c543c 100644 --- a/model/src/main/java/org/oruko/dictionary/service/SearchService.java +++ b/model/src/main/java/org/oruko/dictionary/service/SearchService.java @@ -11,9 +11,9 @@ public interface SearchService { * For getting an entry from the search index by name * * @param nameQuery the name - * @return the nameEntry as a Map or null if name not found + * @return the nameEntry or null if name not found */ - Map getByName(String nameQuery); + NameEntry getByName(String nameQuery); /** * For searching the name entries for a name @@ -32,7 +32,7 @@ public interface SearchService { * * @return the list of names that starts with the given alphabet */ - Set> listByAlphabet(String alphabetQuery); + Set listByAlphabet(String alphabetQuery); /** * For getting the list of partial matches for autocomplete diff --git a/webapi/src/main/java/org/oruko/dictionary/web/NameEntryService.java b/webapi/src/main/java/org/oruko/dictionary/web/NameEntryService.java index 2fc253e4..23e0e08a 100644 --- a/webapi/src/main/java/org/oruko/dictionary/web/NameEntryService.java +++ b/webapi/src/main/java/org/oruko/dictionary/web/NameEntryService.java @@ -8,8 +8,6 @@ import org.oruko.dictionary.model.repository.DuplicateNameEntryRepository; import org.oruko.dictionary.model.repository.NameEntryFeedbackRepository; import org.oruko.dictionary.model.repository.NameEntryRepository; -import org.oruko.dictionary.service.IndexOperationStatus; -import org.oruko.dictionary.service.SearchService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -18,12 +16,8 @@ import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.Objects; import java.util.Optional; -import java.util.Set; /** * The service for managing name entries @@ -31,7 +25,7 @@ * @author Dadepo Aderemi. */ @Service -public class NameEntryService implements SearchService { +public class NameEntryService { private Integer BATCH_SIZE = 50; private Integer PAGE = 0; @@ -350,67 +344,4 @@ private boolean namePresentAsVariant(String name) { return false; }); } - - @Override - public Map getByName(String nameQuery) { - // TODO Confirm that the accents are respected - NameEntry foundName = nameEntryRepository.findByName(nameQuery); - if (Objects.isNull(foundName)) { - return null; - } - return new HashMap() {{ - put(nameQuery, foundName); - }}; - } - - @Override - public Set> search(String searchTerm) { - /** - * The following approach should be taken: - * - * 1. First do a exact search. If found return result. If not go to 2. - * 2. Do a search with ascii-folding. If found return result, if not go to 3 - * 3. Do a prefix search. If found, return result, if not go to 4 - * 4. Do a search based on partial match. Irrespective of outcome, proceed to 4 - * 4a - Using nGram? - * 4b - ? - * 5. Do a full text search against other variants. Irrespective of outcome, proceed to 6 - * 6. Do a full text search against meaning. Irrespective of outcome, proceed to 7 - * 7. Do a full text search against extendedMeaning - */ - return null; - } - - @Override - public Set> listByAlphabet(String alphabetQuery) { - // TODO fetch by alphabet. Respect the accents - return null; - } - - @Override - public List autocomplete(String query) { - // TODO implement, disregarding the accents - return null; - } - - @Override - public Integer getSearchableNames() { - // TODO implement - return null; - } - - @Override - public IndexOperationStatus bulkIndexName(List entries) { - return null; - } - - public IndexOperationStatus deleteFromIndex(String name) { - // TODO implement - return null; - } - - @Override - public IndexOperationStatus bulkDeleteFromIndex(List name) { - return null; - } } diff --git a/webapi/src/main/java/org/oruko/dictionary/web/event/NameDeletedEventHandler.java b/webapi/src/main/java/org/oruko/dictionary/web/event/NameDeletedEventHandler.java index d70a6f5b..16c2d14f 100644 --- a/webapi/src/main/java/org/oruko/dictionary/web/event/NameDeletedEventHandler.java +++ b/webapi/src/main/java/org/oruko/dictionary/web/event/NameDeletedEventHandler.java @@ -15,7 +15,7 @@ @Component public class NameDeletedEventHandler { - @Qualifier("nameEntryService") + @Qualifier("jpaSearchService") @Autowired SearchService nameSearchService; @Autowired diff --git a/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java b/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java index 77e66dc5..a4912170 100644 --- a/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java +++ b/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java @@ -6,6 +6,7 @@ import org.oruko.dictionary.model.NameEntry; import org.oruko.dictionary.model.State; import org.oruko.dictionary.service.IndexOperationStatus; +import org.oruko.dictionary.service.JpaSearchService; import org.oruko.dictionary.web.NameEntryService; import org.oruko.dictionary.web.event.RecentIndexes; import org.oruko.dictionary.web.event.RecentSearches; @@ -49,6 +50,7 @@ public class SearchApi { private Logger logger = LoggerFactory.getLogger(SearchApi.class); private NameEntryService nameEntryService; + private JpaSearchService searchService; private RecentSearches recentSearches; private RecentIndexes recentIndexes; private EventPubService eventPubService; @@ -61,10 +63,14 @@ public class SearchApi { * @param recentIndexes object holding the recent index names in memory */ @Autowired - public SearchApi(EventPubService eventPubService, NameEntryService nameEntryService, - RecentSearches recentSearches, RecentIndexes recentIndexes) { + public SearchApi(EventPubService eventPubService, + NameEntryService nameEntryService, + JpaSearchService searchService, + RecentSearches recentSearches, + RecentIndexes recentIndexes) { this.eventPubService = eventPubService; this.nameEntryService = nameEntryService; + this.searchService = searchService; this.recentSearches = recentSearches; this.recentIndexes = recentIndexes; } @@ -78,7 +84,7 @@ public SearchApi(EventPubService eventPubService, NameEntryService nameEntryServ @RequestMapping(value = "/meta", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity> getMetaData() { Map metaData = new HashMap<>(); - metaData.put("totalPublishedNames", nameEntryService.getSearchableNames()); + metaData.put("totalPublishedNames", searchService.getSearchableNames()); return new ResponseEntity<>(metaData, HttpStatus.OK); } @@ -92,7 +98,7 @@ public ResponseEntity> getMetaData() { public Set> search(@RequestParam(value = "q", required = true) String searchTerm, HttpServletRequest request) { - Set> name = nameEntryService.search(searchTerm); + Set> name = searchService.search(searchTerm); if (name != null && name.size() == 1 && name.stream().allMatch(result -> result.get("name").equals(searchTerm))) { @@ -109,24 +115,24 @@ public List getAutocomplete(@RequestParam(value = "q") Optional } String query = searchQuery.get(); - return nameEntryService.autocomplete(query); + return searchService.autocomplete(query); } @RequestMapping(value = "/alphabet/{alphabet}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) - public Set> getByAlphabet(@PathVariable Optional alphabet) { + public Set getByAlphabet(@PathVariable Optional alphabet) { if (!alphabet.isPresent()) { return Collections.emptySet(); } - return nameEntryService.listByAlphabet(alphabet.get()); + return searchService.listByAlphabet(alphabet.get()); } @RequestMapping(value = "/{searchTerm}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) - public Map findByName(@PathVariable String searchTerm, HttpServletRequest request) { + public NameEntry findByName(@PathVariable String searchTerm, HttpServletRequest request) { - Map name = nameEntryService.getByName(searchTerm); + NameEntry name = searchService.getByName(searchTerm); if (name != null) { eventPubService.publish(new NameSearchedEvent(searchTerm, request.getRemoteAddr())); @@ -257,7 +263,7 @@ public ResponseEntity> batchIndexEntriesByName(@RequestBody return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); } - IndexOperationStatus indexOperationStatus = nameEntryService.bulkIndexName(nameEntries); + IndexOperationStatus indexOperationStatus = searchService.bulkIndexName(nameEntries); updateIsIndexFlag(nameEntries, true, indexOperationStatus); for (NameEntry nameEntry : nameEntries) { @@ -281,7 +287,7 @@ public ResponseEntity> batchIndexEntriesByName(@RequestBody consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity> deleteFromIndex(@PathVariable String name) { - IndexOperationStatus indexOperationStatus = nameEntryService.deleteFromIndex(name); + IndexOperationStatus indexOperationStatus = searchService.deleteFromIndex(name); boolean deleted = indexOperationStatus.getStatus(); String message = indexOperationStatus.getMessage(); Map response = new HashMap<>(); @@ -329,7 +335,7 @@ public ResponseEntity> batchDeleteFromIndex(@RequestBody Str return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); } - IndexOperationStatus indexOperationStatus = nameEntryService.bulkDeleteFromIndex(found); + IndexOperationStatus indexOperationStatus = searchService.bulkDeleteFromIndex(found); updateIsIndexFlag(nameEntries, false, indexOperationStatus); return returnStatusMessage(notFound, indexOperationStatus); } diff --git a/webapi/src/test/java/org/oruko/dictionary/web/rest/SearchApiTest.java b/webapi/src/test/java/org/oruko/dictionary/web/rest/SearchApiTest.java index 70b3a9bc..c88a5331 100644 --- a/webapi/src/test/java/org/oruko/dictionary/web/rest/SearchApiTest.java +++ b/webapi/src/test/java/org/oruko/dictionary/web/rest/SearchApiTest.java @@ -1,20 +1,24 @@ package org.oruko.dictionary.web.rest; -import org.junit.*; +import org.junit.Before; +import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.*; +import org.mockito.InjectMocks; +import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.oruko.dictionary.events.EventPubService; import org.oruko.dictionary.service.SearchService; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import java.util.Collections; - import static org.hamcrest.CoreMatchers.is; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Created by Dadepo Aderemi. @@ -72,7 +76,7 @@ public void test_auto_complete_with_empty_query() throws Exception { @Test public void testFindByName_NameNotFound() throws Exception { - when(searchService.getByName("query")).thenReturn(Collections.emptyMap()); + when(searchService.getByName("query")).thenReturn(null); mockMvc.perform(get("/v1/search/query")) .andExpect(jsonPath("$").isEmpty()) .andExpect(status().isOk()); diff --git a/website/src/main/java/org/oruko/dictionary/DictionaryApplication.java b/website/src/main/java/org/oruko/dictionary/DictionaryApplication.java index 77476347..7352478e 100644 --- a/website/src/main/java/org/oruko/dictionary/DictionaryApplication.java +++ b/website/src/main/java/org/oruko/dictionary/DictionaryApplication.java @@ -77,6 +77,7 @@ public ContentNegotiatingViewResolver viewResolver() { public LocaleResolver localeResolver() { CookieLocaleResolver cookieLocaleResolver = new CookieLocaleResolver(); cookieLocaleResolver.setDefaultLocale(Locale.ENGLISH); + cookieLocaleResolver.setCookieName("lang"); return cookieLocaleResolver; } @@ -123,7 +124,6 @@ public net.sf.ehcache.CacheManager ecacheManager() { return net.sf.ehcache.CacheManager.newInstance(config); } - @Bean public CacheManager cacheManager() { return new EhCacheCacheManager(ecacheManager()); } diff --git a/website/src/main/java/org/oruko/dictionary/website/ApiService.java b/website/src/main/java/org/oruko/dictionary/website/ApiService.java index 8cce30aa..1787126a 100644 --- a/website/src/main/java/org/oruko/dictionary/website/ApiService.java +++ b/website/src/main/java/org/oruko/dictionary/website/ApiService.java @@ -1,7 +1,7 @@ package org.oruko.dictionary.website; +import org.oruko.dictionary.model.NameEntry; import org.springframework.beans.factory.annotation.Value; -import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; @@ -31,12 +31,12 @@ public List> getAllNames() { return Arrays.asList(restTemplate.getForObject(APIPATH + "/names", Map[].class)); } - @Cacheable("querySearchResult") - public Map getName(String nameQuery) { - return restTemplate.getForObject(APIPATH + "/search/" + nameQuery, Map.class); + //@Cacheable("querySearchResult") + public NameEntry getName(String nameQuery) { + return restTemplate.getForObject(APIPATH + "/search/" + nameQuery, NameEntry.class); } - @Cacheable("names") + //@Cacheable("names") public List> searchName(String nameQuery) { return Arrays.asList(restTemplate.getForObject(APIPATH + "/search/?q=" + nameQuery, Map[].class)); } diff --git a/website/src/main/java/org/oruko/dictionary/website/SearchResultController.java b/website/src/main/java/org/oruko/dictionary/website/SearchResultController.java index 17f62fc7..910ccd86 100644 --- a/website/src/main/java/org/oruko/dictionary/website/SearchResultController.java +++ b/website/src/main/java/org/oruko/dictionary/website/SearchResultController.java @@ -1,6 +1,7 @@ package org.oruko.dictionary.website; import org.apache.commons.lang3.StringUtils; +import org.oruko.dictionary.model.NameEntry; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; @@ -52,7 +53,7 @@ public String showEntry(@PathVariable String nameEntry, Model map) { map.addAttribute("title", "Name Entry"); map.addAttribute("host", host); if (!map.containsAttribute("name")) { - final Map name = apiService.getName(nameEntry); + final NameEntry name = apiService.getName(nameEntry); if (name == null) { // no single entry found for query, return to view where search result can be displayed return "redirect:/entries?q=" + nameEntry; diff --git a/website/src/main/resources/application.properties b/website/src/main/resources/application.properties index e77342bd..308c39a7 100644 --- a/website/src/main/resources/application.properties +++ b/website/src/main/resources/application.properties @@ -26,7 +26,7 @@ endpoints.shutdown.enabled=true # Template (Handlebars) handlebars.prefix: classpath:website/ handlebars.suffix: .hbs -handlebars.cache: true +handlebars.cache: false handlebars.registerMessageHelper: true handlebars.failOnMissingFile: false handlebars.prettyPrint: false diff --git a/website/src/main/resources/website/contactus.hbs b/website/src/main/resources/website/contactus.hbs index d782d2ff..29bf1d19 100644 --- a/website/src/main/resources/website/contactus.hbs +++ b/website/src/main/resources/website/contactus.hbs @@ -6,60 +6,31 @@
-
-
- -
-
-

Contact Information

- -

We're easy to get through to, whether in person or by mail.

- - - - -

You may send an email to project@yorubaname.com

-

Or visit our office at YorubaName.com
- 2nd Floor, The Garnet Building
- Lekki - Epe Express Road, Lekki
- Lagos 101245
- Nigeria -

- -

Follow us on Social Media

- - -
-
+
+
+ +
+
+

{{message "lang.contact-info"}}

+ +

{{message "lang.contact-info-preamble"}}

+ +

{{message "lang.contact-info-email-preamble"}} project@yorubaname.com

+

Or visit our office at YorubaName.com
+ 2nd Floor, The Garnet Building
+ Lekki - Epe Express Road, Lekki
+ Lagos 101245
+ Nigeria +

+ +

Follow us on Social Media

+ + +
+
diff --git a/website/src/main/resources/website/singleresult.hbs b/website/src/main/resources/website/singleresult.hbs index 019324b0..0f7c0730 100644 --- a/website/src/main/resources/website/singleresult.hbs +++ b/website/src/main/resources/website/singleresult.hbs @@ -9,7 +9,7 @@
-

{{normalize name.name }}

+

{{name.name }}

{{message "lang.pronounciation"}}



-

{{message "lang.meaningof"}} {{normalize name.name }}

+

{{message "lang.meaningof"}} {{name.name }}

{{ name.meaning }}



From 2aa918d90e4f83fa56f779800296253c6ff1c94b Mon Sep 17 00:00:00 2001 From: Dadepo Aderemi Date: Sat, 11 Nov 2017 20:59:56 +0100 Subject: [PATCH 04/12] Enabled Yoruba translation --- .../dictionary/DictionaryApplication.java | 14 ++- .../src/main/resources/application.properties | 2 +- .../src/main/resources/messages.properties | 19 +++ .../src/main/resources/messages_yo.properties | 90 +++++++++++++- .../resources/public/assets/css/overwrite.css | 5 + .../resources/public/assets/js/scripts.js | 20 ++++ .../src/main/resources/website/contactus.hbs | 79 ++++--------- website/src/main/resources/website/error.hbs | 15 ++- website/src/main/resources/website/header.hbs | 5 +- website/src/main/resources/website/home.hbs | 36 +++--- .../resources/website/minisearchcomponent.hbs | 3 +- .../src/main/resources/website/volunteer.hbs | 111 +++++++----------- 12 files changed, 241 insertions(+), 158 deletions(-) diff --git a/website/src/main/java/org/oruko/dictionary/DictionaryApplication.java b/website/src/main/java/org/oruko/dictionary/DictionaryApplication.java index 77476347..8ffe8912 100644 --- a/website/src/main/java/org/oruko/dictionary/DictionaryApplication.java +++ b/website/src/main/java/org/oruko/dictionary/DictionaryApplication.java @@ -10,6 +10,7 @@ import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.ehcache.EhCacheCacheManager; import org.springframework.context.annotation.Bean; +import org.springframework.context.support.ReloadableResourceBundleMessageSource; import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; @@ -35,6 +36,8 @@ //@EnableWebSecurity //switches off auto configuration for spring security public class DictionaryApplication extends WebMvcConfigurerAdapter { + private final String LANG = "lang"; + /** * Main method used to kick start and run the application * @param args arguments supplied to the application @@ -77,16 +80,25 @@ public ContentNegotiatingViewResolver viewResolver() { public LocaleResolver localeResolver() { CookieLocaleResolver cookieLocaleResolver = new CookieLocaleResolver(); cookieLocaleResolver.setDefaultLocale(Locale.ENGLISH); + cookieLocaleResolver.setCookieName(LANG); return cookieLocaleResolver; } @Bean public LocaleChangeInterceptor localeChangeInterceptor() { LocaleChangeInterceptor lci = new LocaleChangeInterceptor(); - lci.setParamName("lang"); + lci.setParamName(LANG); return lci; } + @Bean + public ReloadableResourceBundleMessageSource messageSource() { + ReloadableResourceBundleMessageSource source = new ReloadableResourceBundleMessageSource(); + source.setDefaultEncoding("UTF-8"); + source.setBasename("classpath:/messages"); + return source; + } + @Bean public net.sf.ehcache.CacheManager ecacheManager() { diff --git a/website/src/main/resources/application.properties b/website/src/main/resources/application.properties index e77342bd..77f8ad17 100644 --- a/website/src/main/resources/application.properties +++ b/website/src/main/resources/application.properties @@ -11,7 +11,7 @@ spring.datasource.sql-script-encoding=UTF-8 spring.jpa.database=MYSQL spring.jpa.show-sql=true -spring.jpa.hibernate.ddl-auto=create +spring.jpa.hibernate.ddl-auto=validate #Server diff --git a/website/src/main/resources/messages.properties b/website/src/main/resources/messages.properties index 52cae5ac..6c3d7218 100644 --- a/website/src/main/resources/messages.properties +++ b/website/src/main/resources/messages.properties @@ -69,3 +69,22 @@ lang.about-tts=Audio Prompts: Hearing the Names lang.join-us-lex=Join us as a Lexicographer! lang.lets-build-together=Let's build up this database together lang.how-to-support=I would like to support you in some way… but how? + + +lang.contact-info=Contact Information +lang.contact-info-preamble=We're easy to get through to, whether in person or by mail. +lang.contact-info-email-preamble=You may send an email to +lang.contact-info-contact-preamble=Or visit our office at YorubaName.com +lang.social-media=Follow us on Social Media + +lang.error-page-not-found=Oops. Sure you got the right link? +lang.error-message1=It seems like we can't find the resource you're looking for, or it's been moved. +lang.error-message2=Why not click here to go back to the home page to discover more Yoruba names? + +lang.oni-message=A phenomenal concept that will keep the Yorùbá culture alive. I strongly support the endeavor. +lang.key-supporters=Our key supporters +lang.yoruba-name-dictionary=Yoruba Names Dictionary + +lang.yoruba-name-newsletter=YorubaName Newsletter +lang.follow-update=Follow us behind the scenes for exclusive news, freebies, links, lexicographer tools, and other important updates about this dictionary. +lang.enter-your-email=Enter your email here \ No newline at end of file diff --git a/website/src/main/resources/messages_yo.properties b/website/src/main/resources/messages_yo.properties index 616422d5..601b19ea 100644 --- a/website/src/main/resources/messages_yo.properties +++ b/website/src/main/resources/messages_yo.properties @@ -1 +1,89 @@ -lang.welcome=Eku Abo \ No newline at end of file +lang.welcome=Ẹ káàbọ̀ +lang.home=Ilé +lang.about=Nípa Wa +lang.donate=Rànwálọ́wọ́ +lang.volunteer=Darapọ̀ Mọ́ Wa +lang.logreg=Forúkọ Sílẹ̀ +lang.contact=Kàn Sí Wa +lang.blog=Búlọ́gì +lang.language=Èdè +lang.search-plchol=Tẹ Orúkọ Yorùbá tó bá wù ẹ́ síbí kí o sì tẹ “Enter” +lang.jumbotron1=Ó ju +lang.jumbotron2=Orùkọ Yorùbá lọ tí a ní níbí +lang.kybtitle=Tẹ ibí fún bọ́tìnì alámì +lang.adv-search=Àwárí Ìjìnlẹ̀ +lang.jumboh31=Darapọ̀ mọ́ àwọn olùlò yókù láti fi orúkọ mìíràn kún-un +lang.jumboh32=Fún wa ní orúkọ kan +lang.latest-search=Àwárí Tuntun +lang.latest-add=Àfikún Tuntun +lang.most-popular=Gbajúmọ̀ Orúkọ +lang.most-popular-names=Àwọn Orúkọ Tó Gbajúmọ̀ Jù +lang.name-of-day=Orúkọ Ojúmọ́ +lang.play-sound=Tẹ̀ láti gbọ́ ohùn +lang.submitby=Ẹni tó fún wa ni +lang.pronounciation=Sísọ síta +lang.share=Fihànká +lang.improve-entry=Tún orúkọ yìí ṣe +lang.meaningof=Ìtumọọ +lang.extendedmeaningof=Àwọn àlàyé mìíràn +lang.storyof=Ìtàn díẹ̀ nípa orúkọ yìí +lang.morphology=Ìtúpalẹ̀ Mọ́fímù +lang.etymology=Ìtumọ̀ ẹyọ-ẹyọ +lang.geolocation=Agbègbè +lang.variants=Irúurú +lang.see-also=Ẹ tún wo +lang.entryfoundfor=A rí àwọn orúkọ fún - +lang.commonin=Ó pọ̀ ní +lang.number-of-names=Iye àwọn orúkọ +lang.name-listed-by-alphabets=Awọn orúkọ tí a kọ ní A-B-D +lang.famous-persons=Àwọn Ènìyàn Gbajúọ̀ +lang.media-links=Ibi tí a ti lè kà síi + +lang.submitname.heading=Fi orúkọ tuntun sílẹ̀ +lang.submitname.entername=Tẹ orúkọ tuntun síbí +lang.submitname.enternameplch=Tẹ orúkọ tí a gbèrò ẹ síbí... +lang.submitname.entermeaning=Ti o bá mọ̀, jọ̀ọ́ sọ ìtumọ̀ àti/tàbí ìtàn nípa orúkọ yìí +lang.submitname.enterlocation=Níbo lo ti gbọ́ tàbí tí wọ́n ti wọ́n ti fún ẹ lórúkọ yìí. +lang.submitname.enteremail=Àdírẹ́sì ímeèlì tí o fẹ́ ká ti sọ fún ẹ tí orúkọ yìí bá ti wọlé +lang.submitname.nameexists=A ti ní orúkọ yìí tẹ́lẹ̀ +lang.submitname.viewentry=Wo Orúkọ + +lang.improve-entry.help-us=Bá wa ṣe àtúnṣe síi +lang.improve-entry.sub-text=Bóyá o tiẹ̀ ní ìtàn kan láti sọ fún wa nípa orúkọ yìí, tàbí fún wa ní ǹkan tí a ṣì kọ, tàbí fikún oun kan tí a kọ sílẹ̀. + +lang.entry-not-found.no-match=Págà! A kò rí oun tó jọ +lang.entry-not-found.suggest-text=Ǹjẹ́ o fẹ́ràn láti fi orúkọ kan kún àwọn orúkọ wa? Bá wa dáhùn àwọn ìbéèrè wọ̀nyí +lang.entry-not-found.suggest-pretext= Ǹjẹ́ o fẹ́ràn láti fi orúkọ kún àwọn orúkọ wa? +lang.entry-not-found.similar-names=Wò níbí àwọn orúkọ tó lè jọ ara wọn +lang.go-to-suggest-linktext=Jọ̀ọ́ lọ sí abala ibi tí a ti ń forúkọ sílẹ̀. +lang.entry-not-found.minikeyb-text=Jọ̀ọ́ lo oun ìtẹ̀wé Yorùbá tí a pèsè láti lo ààmì. + +lang.join-the-team=Darapọ̀ mọ́ wa +lang.amazing-team=Àwọn Ọmọ Ẹgbẹ́ wa +lang.what-is-yorubaname=Kíni YorubaName.com +lang.how-to-use=Láti lo Dikṣọ́nárì Yìí +lang.work-in-progress=Gbobo orúkọ̀ tó wà níbi jẹ́ Iṣẹ́-tí-a-ṣì-ńṣe- Lọ́wọ́ +lang.about-tone-mark=Ṣé ká fi ààmì síi tàbí bẹ́ẹ̀ kọ̀? +lang.how-names-collected=Báwọ ni a ṣe ń gba àwọn orúkọ wọ̀nyí? +lang.about-tts=Sísọ Síta: Gbígbọ́ Àwọn Orúkọ Wọ̀nyí +lang.join-us-lex=Darapọ̀ Mọ́ Wa Gẹ́gẹ́ Bíi Onímọ̀ Ìjìnlẹ̀ Orúkọ +lang.lets-build-together=Ẹ jẹ́ ká jọ mú orúkọ yìí kún papọ̀ +lang.how-to-support=Mo fẹ́ darapọ̀ pẹ̀lú yín lọ́nà kan tàbí òmíràn… ṣùgbọ́n báwo? + +lang.contact-info=Ibi tí ẹ ti lè kà sí wa +lang.contact-info-preamble=Ó rọrùn láti kàn sí wa, bóyá nípa ara-sára tàbi nípa ímeèlì. +lang.contact-info-email-preamble=Ẹ le fi ímeèlì ránṣẹ́ sí +lang.contact-info-contact-preamble=Tàbí kàn sí ilé iṣẹ́ wa ní YorubaName.com +lang.social-media=Ẹ tẹ̀lé wa lórí ẹ̀rọ ayélujára + +lang.error-page-not-found=Págà. Ǹjẹ́ o rò pé ọ̀nà tó tọ́ lo tẹ̀? +lang.error-message1=Ó dàbíi pé a kò rí àwọn ǹkan tí o ń wá, tàbí wọ́n ti sún-un lọ síbòmíì. +lang.error-message2=Oòṣe tẹ ibí yìí kí o padà sí abala ilé láti tún ṣàbápàdé àwọn orúkọ Yorùbá mìíràn? + +lang.oni-message=Iṣẹ́ gbògí tó ṣe pàtàki gidi tí yóò sì mú kí èdè Yorùbá tẹ̀síwájú ni. Mo faramọ́ọ gidigidi. +lang.key-supporters=Àwọn agbèlẹ́yìn wa pàtàkì +lang.yoruba-name-dictionary=Dikṣọ́nárì Orúkọ Yorùbá + +lang.yoruba-name-newsletter=Ìwé Ìròyìn YorubaName +lang.follow-update=Follow us behind the scenes for exclusive news, freebies, links, lexicographer tools, and other important updates about this dictionary. +lang.enter-your-email=Enter your email here \ No newline at end of file diff --git a/website/src/main/resources/public/assets/css/overwrite.css b/website/src/main/resources/public/assets/css/overwrite.css index 4a51f6e0..47d49edc 100644 --- a/website/src/main/resources/public/assets/css/overwrite.css +++ b/website/src/main/resources/public/assets/css/overwrite.css @@ -261,3 +261,8 @@ a.btn,.btn:focus { background-color: #f9f9f9; } + +#lang-group.nav>li>a { + padding: 7px 12px !important; + font-weight: normal; +} diff --git a/website/src/main/resources/public/assets/js/scripts.js b/website/src/main/resources/public/assets/js/scripts.js index dfa3e7b9..ebdc271e 100644 --- a/website/src/main/resources/public/assets/js/scripts.js +++ b/website/src/main/resources/public/assets/js/scripts.js @@ -17,6 +17,26 @@ $(function(){ + + + $("ul#lang-group>li.active").removeClass("active"); + var selectedLang; + if (document.cookie.indexOf("lang=") === -1) { + selectedLang = 'en'; + } else { + for (i = 0; i < document.cookie.split(";").length; i++) { + var str = document.cookie.split(";")[i]; + if (str.trim().startsWith("lang=")) { + var langEntry = str.split("="); + selectedLang = langEntry[1]; + console.log(selectedLang); + break; + } + } + } + + $('li#'+selectedLang).addClass("active"); + $('[data-toggle="tooltip"]').tooltip(); diff --git a/website/src/main/resources/website/contactus.hbs b/website/src/main/resources/website/contactus.hbs index d782d2ff..29bf1d19 100644 --- a/website/src/main/resources/website/contactus.hbs +++ b/website/src/main/resources/website/contactus.hbs @@ -6,60 +6,31 @@
-
-
- -
-
-

Contact Information

- -

We're easy to get through to, whether in person or by mail.

- - - - -

You may send an email to project@yorubaname.com

-

Or visit our office at YorubaName.com
- 2nd Floor, The Garnet Building
- Lekki - Epe Express Road, Lekki
- Lagos 101245
- Nigeria -

- -

Follow us on Social Media

- - -
-
+
+
+ +
+
+

{{message "lang.contact-info"}}

+ +

{{message "lang.contact-info-preamble"}}

+ +

{{message "lang.contact-info-email-preamble"}} project@yorubaname.com

+

Or visit our office at YorubaName.com
+ 2nd Floor, The Garnet Building
+ Lekki - Epe Express Road, Lekki
+ Lagos 101245
+ Nigeria +

+ +

Follow us on Social Media

+ + +
+
diff --git a/website/src/main/resources/website/error.hbs b/website/src/main/resources/website/error.hbs index 22a36463..91556112 100644 --- a/website/src/main/resources/website/error.hbs +++ b/website/src/main/resources/website/error.hbs @@ -1,12 +1,11 @@ {{> header }}
-
-
-

Oops. Sure you got the right link?

-

It seems like we can't find the resource you're looking for, or it's been moved.

-

Why not click here to go back to the home page to discover more Yoruba names?

-
- -
+
+
+

{{message "lang.error-page-not-found"}}

+

{{message "lang.error-message1"}}

+

{{message "lang.error-message2"}}

+
+
\ No newline at end of file diff --git a/website/src/main/resources/website/header.hbs b/website/src/main/resources/website/header.hbs index 2ae938ca..3717e9bb 100644 --- a/website/src/main/resources/website/header.hbs +++ b/website/src/main/resources/website/header.hbs @@ -57,13 +57,16 @@
diff --git a/website/src/main/resources/website/home.hbs b/website/src/main/resources/website/home.hbs index b465d2f2..ee901e06 100644 --- a/website/src/main/resources/website/home.hbs +++ b/website/src/main/resources/website/home.hbs @@ -7,7 +7,9 @@

{{message "lang.jumbotron1"}} {{nameCount}} {{message "lang.jumbotron2"}}

{{> searchcomponent }} -

"A phenomenal concept that will keep the Yorùbá culture alive. I strongly support the endeavor."
— Ọọ̀ni Adéyẹyè Ẹniìtàn Ògúnwùsì (Ọ̀jájá II. Ọọ̀ni of Ifẹ̀ Kingdom)

+

{{message "lang.oni-message"}}
— Ọọ̀ni Adéyẹyè Ẹniìtàn Ògúnwùsì (Ọ̀jájá II + .Ọọ̀ni of Ifẹ̀ Kingdom) +

{{message "lang.jumboh31"}}

{{message "lang.jumboh32"}} @@ -60,21 +62,17 @@
-

Our key supporters

-
- +

{{message "lang.key-supporters"}}

+
+
-
- +
-
- -
-
- -
-
- +
@@ -84,8 +82,7 @@ data-adapt-container-width="true" data-hide-cover="false" data-show-facepile="false" data-show-posts="false">
@@ -93,12 +90,11 @@ diff --git a/website/src/main/resources/website/minisearchcomponent.hbs b/website/src/main/resources/website/minisearchcomponent.hbs index d3ea77e9..4515b6bc 100644 --- a/website/src/main/resources/website/minisearchcomponent.hbs +++ b/website/src/main/resources/website/minisearchcomponent.hbs @@ -1,4 +1,3 @@
- - +
\ No newline at end of file diff --git a/website/src/main/resources/website/volunteer.hbs b/website/src/main/resources/website/volunteer.hbs index 8a17489a..414154ab 100644 --- a/website/src/main/resources/website/volunteer.hbs +++ b/website/src/main/resources/website/volunteer.hbs @@ -6,77 +6,48 @@
-
+
+ +
+ +
+

Admin/Volunteers

+ +

Lane 1. Lexicographic related tasks

+

A. Field work

+

We seek scholars (Nigerians and non-Nigerians) working in remote parts of the Yoruba countryside in Nigeria either as students (M.A, PhD, etc) or as health (or social service) workers, or in any other department. Their role will be to help with the gathering of names and (more importantly) of meanings, stories, legends, and other ethnographic and anthropological information.

+

Required skills:

+

Fieldwork interview skills, interpersonal skills, work with database (e.g. Microsoft Excel, etc)

+

B. Annotating, Proofreading, Digitizing

+

We seek Yoruba language scholars, students, enthusiasts, and teachers for freelance work in annotating, editing, and moderating entries in the dictionary, along with any other work relating to lexicography. Their work will build on the work of fieldworkers.

+

Required skills:

+

General computer knowledge, deep bilingual proficiency in Yoruba and English, a knowledge of Yoruba names, and/or a willingness to research.

+

C. Tone Marking and IPA

+

We seek linguists with knowledge of Yoruba phonology to help with lexicographic work relating to verifying tones on entries, tone-marking new entries into the database, and providing IPA notation for all the words in the dictionary.

+

Required skills:

+

General computer knowledge, knowledge of IPA/linguistics and Yoruba.

+

D. Voice over tasks

+

We seek female Yoruba speakers, with professional speaking competence, to help with pronouncing all the names in the dictionary. If in Nigeria, they must live in Ibadan or Lagos (or have electronic recording equipment of their own, if they live elsewhere).

+

Lane 2. Social media related tasks

+

A. Social Media/PR

+

We seek one or two people interested in helping out in our PR department, which includes social media (Facebook, Twitter, Instagram), newsletter, blog, and other outreaches.

+

Required skills:

+

Knowledge of social media, proficiency in Yoruba, knowledge of basic computer design skills.

+

B. Social Media/Outreach

+

We seek people to help with public outreach. We seek more institutional support (from universities, language organizations, studios, institutes, and other relevant organizations.
+ Required skills: local and international contacts/connection.

+

Lane 3. Software development related tasks

+

To maintain and extend the software powering the dictionary, we seek developers who are proficient with Java programming and/or JavaScript programming with experience or interest in working with AngularJs

+

Required skills:

+

Proficient in Java or Javascript. Knowledge of Spring framework or AngularJs

+

 

+

All these positions are on a volunteer basis. We are not – for now – able to offer financial incentive. There are other incentives in the work, however, which are not financial, but we hope that your decision to join us will be because you believe in the work and want to see it succeed.

+

Send an email to volunteer@yorubaname.com with the word “volunteer” and the specific lane and category in the subject line. For instance: “Volunteer 3A” for those interested in the PR position.

+ +
+
+
-
- -
-

Admin/Volunteers

-

-

Lane 1. Lexicographic related tasks

-

A. Field work

-

We seek scholars (Nigerians and non-Nigerians) working in remote parts of the Yoruba countryside in Nigeria either as students (M.A, PhD, etc) or as health (or social service) workers, or in any other department. Their role will be to help with the gathering of names and (more importantly) of meanings, stories, legends, and other ethnographic and anthropological information.

-

Required skills:

-

Fieldwork interview skills, interpersonal skills, work with database (e.g. Microsoft Excel, etc)

-

B. Annotating, Proofreading, Digitizing

-

We seek Yoruba language scholars, students, enthusiasts, and teachers for freelance work in annotating, editing, and moderating entries in the dictionary, along with any other work relating to lexicography. Their work will build on the work of fieldworkers.

-

Required skills:

-

General computer knowledge, deep bilingual proficiency in Yoruba and English, a knowledge of Yoruba names, and/or a willingness to research.

-

C. Tone Marking and IPA

-

We seek linguists with knowledge of Yoruba phonology to help with lexicographic work relating to verifying tones on entries, tone-marking new entries into the database, and providing IPA notation for all the words in the dictionary.

-

Required skills:

-

General computer knowledge, knowledge of IPA/linguistics and Yoruba.

-

D. Voice over tasks

-

We seek female Yoruba speakers, with professional speaking competence, to help with pronouncing all the names in the dictionary. If in Nigeria, they must live in Ibadan or Lagos (or have electronic recording equipment of their own, if they live elsewhere).

-

Lane 2. Social media related tasks

-

A. Social Media/PR

-

We seek one or two people interested in helping out in our PR department, which includes social media (Facebook, Twitter, Instagram), newsletter, blog, and other outreaches.

-

Required skills:

-

Knowledge of social media, proficiency in Yoruba, knowledge of basic computer design skills.

-

B. Social Media/Outreach

-

We seek people to help with public outreach. We seek more institutional support (from universities, language organizations, studios, institutes, and other relevant organizations.
- Required skills: local and international contacts/connection.

-

Lane 3. Software development related tasks

-

To maintain and extend the software powering the dictionary, we seek developers who are proficient with Java programming and/or JavaScript programming with experience or interest in working with AngularJs

-

Required skills:

-

Proficient in Java or Javascript. Knowledge of Spring framework or AngularJs

-

 

-

All these positions are on a volunteer basis. We are not – for now – able to offer financial incentive. There are other incentives in the work, however, which are not financial, but we hope that your decision to join us will be because you believe in the work and want to see it succeed.

-

Send an email to volunteer@yorubaname.com with the word “volunteer” and the specific lane and category in the subject line. For instance: “Volunteer 3A” for those interested in the PR position.

- -
-
-
-
From b4b2737dafb32e93df58d8c245847fd0916b478f Mon Sep 17 00:00:00 2001 From: Dadepo Aderemi Date: Sat, 11 Nov 2017 21:15:56 +0100 Subject: [PATCH 05/12] Implemented getting number of searchable names --- .../oruko/dictionary/model/repository/NameEntryRepository.java | 1 + .../java/org/oruko/dictionary/service/JpaSearchService.java | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/model/src/main/java/org/oruko/dictionary/model/repository/NameEntryRepository.java b/model/src/main/java/org/oruko/dictionary/model/repository/NameEntryRepository.java index 7a1b83c7..bc113725 100644 --- a/model/src/main/java/org/oruko/dictionary/model/repository/NameEntryRepository.java +++ b/model/src/main/java/org/oruko/dictionary/model/repository/NameEntryRepository.java @@ -41,4 +41,5 @@ public interface NameEntryRepository extends JpaRepository { // Search related repository access Set findByNameStartingWithAndState(String alphabet, State state); NameEntry findByNameAndState(String alphabet, State state); + Integer countByState(State state); } diff --git a/model/src/main/java/org/oruko/dictionary/service/JpaSearchService.java b/model/src/main/java/org/oruko/dictionary/service/JpaSearchService.java index d3e0911b..93ccb94a 100644 --- a/model/src/main/java/org/oruko/dictionary/service/JpaSearchService.java +++ b/model/src/main/java/org/oruko/dictionary/service/JpaSearchService.java @@ -55,8 +55,7 @@ public List autocomplete(String query) { @Override public Integer getSearchableNames() { - // TODO implement - return null; + return nameEntryRepository.countByState(State.PUBLISHED); } @Override From afb7b9a0c22bcd1a0dfcf22c3bfbcde8b0dc8cc4 Mon Sep 17 00:00:00 2001 From: Dadepo Aderemi Date: Fri, 1 Dec 2017 20:34:18 +0100 Subject: [PATCH 06/12] wipp --- .../elasticsearch/ElasticSearchServicex.java | 2 +- .../main/java/org/oruko/dictionary/model/State.java | 4 ++++ .../model/repository/NameEntryRepository.java | 1 + .../oruko/dictionary/service/JpaSearchService.java | 11 ++++++++--- .../org/oruko/dictionary/service/SearchService.java | 2 +- .../dictionary/web/event/NameDeletedEventHandler.java | 2 +- .../java/org/oruko/dictionary/web/rest/SearchApi.java | 2 +- .../org/oruko/dictionary/DictionaryApplication.java | 2 +- 8 files changed, 18 insertions(+), 8 deletions(-) diff --git a/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchServicex.java b/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchServicex.java index 025024e1..f6758376 100644 --- a/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchServicex.java +++ b/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchServicex.java @@ -287,7 +287,7 @@ public IndexOperationStatus bulkIndexName(List entries) { * @param name the name to delete from the index * @return true | false depending on if deleting operation was successful */ - public IndexOperationStatus deleteFromIndex(String name) { + public IndexOperationStatus removeFromIndex(String name) { return null; // if (!isElasticSearchNodeAvailable()) { // diff --git a/model/src/main/java/org/oruko/dictionary/model/State.java b/model/src/main/java/org/oruko/dictionary/model/State.java index e5d51064..1af65205 100644 --- a/model/src/main/java/org/oruko/dictionary/model/State.java +++ b/model/src/main/java/org/oruko/dictionary/model/State.java @@ -15,6 +15,10 @@ public enum State { * Name has been indexed in the search engine, thus publish and users can find it when they search for it */ PUBLISHED, + /** + * A name that was initially published but has been removed from the index + */ + UNPUBLISHED, /** * A name that has been published (indexed into the search engine) was modified, thus needs to be re-indexed */ diff --git a/model/src/main/java/org/oruko/dictionary/model/repository/NameEntryRepository.java b/model/src/main/java/org/oruko/dictionary/model/repository/NameEntryRepository.java index bc113725..b5a6dbf2 100644 --- a/model/src/main/java/org/oruko/dictionary/model/repository/NameEntryRepository.java +++ b/model/src/main/java/org/oruko/dictionary/model/repository/NameEntryRepository.java @@ -42,4 +42,5 @@ public interface NameEntryRepository extends JpaRepository { Set findByNameStartingWithAndState(String alphabet, State state); NameEntry findByNameAndState(String alphabet, State state); Integer countByState(State state); + Boolean deleteByNameAndState(String name, State state); } diff --git a/model/src/main/java/org/oruko/dictionary/service/JpaSearchService.java b/model/src/main/java/org/oruko/dictionary/service/JpaSearchService.java index 93ccb94a..6765d5eb 100644 --- a/model/src/main/java/org/oruko/dictionary/service/JpaSearchService.java +++ b/model/src/main/java/org/oruko/dictionary/service/JpaSearchService.java @@ -63,9 +63,14 @@ public IndexOperationStatus bulkIndexName(List entries) { return null; } - public IndexOperationStatus deleteFromIndex(String name) { - // TODO implement - return null; + public IndexOperationStatus removeFromIndex(String name) { + NameEntry foundName = nameEntryRepository.findByNameAndState(name, State.PUBLISHED); + if (foundName == null) { + return new IndexOperationStatus(false, "Published Name not found"); + } + foundName.setState(State.UNPUBLISHED); + nameEntryRepository.save(foundName); + return new IndexOperationStatus(true, name + " removed from index"); } @Override diff --git a/model/src/main/java/org/oruko/dictionary/service/SearchService.java b/model/src/main/java/org/oruko/dictionary/service/SearchService.java index 0a6c543c..007ed3e9 100644 --- a/model/src/main/java/org/oruko/dictionary/service/SearchService.java +++ b/model/src/main/java/org/oruko/dictionary/service/SearchService.java @@ -45,6 +45,6 @@ public interface SearchService { Integer getSearchableNames(); IndexOperationStatus bulkIndexName(List entries); - IndexOperationStatus deleteFromIndex(String name); + IndexOperationStatus removeFromIndex(String name); IndexOperationStatus bulkDeleteFromIndex(List name); } diff --git a/webapi/src/main/java/org/oruko/dictionary/web/event/NameDeletedEventHandler.java b/webapi/src/main/java/org/oruko/dictionary/web/event/NameDeletedEventHandler.java index 16c2d14f..aaa5bfb6 100644 --- a/webapi/src/main/java/org/oruko/dictionary/web/event/NameDeletedEventHandler.java +++ b/webapi/src/main/java/org/oruko/dictionary/web/event/NameDeletedEventHandler.java @@ -28,7 +28,7 @@ public class NameDeletedEventHandler { public void listen(NameDeletedEvent event) { // Handle when a name is deleted try { - nameSearchService.deleteFromIndex(event.getName()); + nameSearchService.removeFromIndex(event.getName()); recentIndexes.remove(event.getName()); recentSearches.remove(event.getName()); } catch (Exception e) { diff --git a/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java b/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java index a4912170..ed4eb1b0 100644 --- a/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java +++ b/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java @@ -287,7 +287,7 @@ public ResponseEntity> batchIndexEntriesByName(@RequestBody consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity> deleteFromIndex(@PathVariable String name) { - IndexOperationStatus indexOperationStatus = searchService.deleteFromIndex(name); + IndexOperationStatus indexOperationStatus = searchService.removeFromIndex(name); boolean deleted = indexOperationStatus.getStatus(); String message = indexOperationStatus.getMessage(); Map response = new HashMap<>(); diff --git a/website/src/main/java/org/oruko/dictionary/DictionaryApplication.java b/website/src/main/java/org/oruko/dictionary/DictionaryApplication.java index 17f1f0bd..55e9db77 100644 --- a/website/src/main/java/org/oruko/dictionary/DictionaryApplication.java +++ b/website/src/main/java/org/oruko/dictionary/DictionaryApplication.java @@ -100,7 +100,7 @@ public ReloadableResourceBundleMessageSource messageSource() { } - @Bean +// @Bean public net.sf.ehcache.CacheManager ecacheManager() { CacheConfiguration allNames = new CacheConfiguration(); allNames.setName("allNames"); From d2d171db8f25203e3aa5478ea725b50743367ab6 Mon Sep 17 00:00:00 2001 From: Dadepo Aderemi Date: Sat, 2 Dec 2017 04:34:22 +0100 Subject: [PATCH 07/12] Initial changes to have a JPA powered search --- .../auth/util/ApiUsersDatabaseImporter.java | 2 +- .../oruko/dictionary/util/DataImporter.java | 2 +- .../elasticsearch/ElasticSearchServicex.java | 17 +++-- .../model/repository/NameEntryRepository.java | 10 ++- .../dictionary/service/JpaSearchService.java | 65 ++++++++++++++++--- .../dictionary/service/SearchService.java | 6 +- .../oruko/dictionary/web/rest/SearchApi.java | 18 ++--- .../website/SearchResultController.java | 1 - 8 files changed, 89 insertions(+), 32 deletions(-) diff --git a/auth/src/main/java/org/oruko/dictionary/auth/util/ApiUsersDatabaseImporter.java b/auth/src/main/java/org/oruko/dictionary/auth/util/ApiUsersDatabaseImporter.java index 50939ebf..306be2c7 100644 --- a/auth/src/main/java/org/oruko/dictionary/auth/util/ApiUsersDatabaseImporter.java +++ b/auth/src/main/java/org/oruko/dictionary/auth/util/ApiUsersDatabaseImporter.java @@ -40,7 +40,7 @@ public void initApiUsers() { /** * Only initialize the database only when in dev */ - if (host.equalsIgnoreCase("localhost")) { + if (!host.equalsIgnoreCase("localhost")) { // lexi user ApiUser lexicographer = new ApiUser(); diff --git a/bootstrap/src/main/java/org/oruko/dictionary/util/DataImporter.java b/bootstrap/src/main/java/org/oruko/dictionary/util/DataImporter.java index f9cc6b22..b7aeffb5 100644 --- a/bootstrap/src/main/java/org/oruko/dictionary/util/DataImporter.java +++ b/bootstrap/src/main/java/org/oruko/dictionary/util/DataImporter.java @@ -43,7 +43,7 @@ public void initializeData() { * Only initialize the database only when in dev * //TODO move this to profiles */ - if (host.equalsIgnoreCase("localhost")) { + if (!host.equalsIgnoreCase("localhost")) { List nameEntries = initializeDb(); } } diff --git a/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchServicex.java b/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchServicex.java index f6758376..6c98cc92 100644 --- a/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchServicex.java +++ b/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchServicex.java @@ -98,7 +98,7 @@ public NameEntry getByName(String nameQuery) { * @return the list of entries found */ @Override - public Set> search(String searchTerm) { + public Set search(String searchTerm) { /** * 1. First do a exact search. If found return result. If not go to 2. * 2. Do a search with ascii-folding. If found return result, if not go to 3 @@ -121,7 +121,7 @@ public Set> search(String searchTerm) { }); if (result.size() == 1) { - return result; + return null; } } @@ -133,7 +133,7 @@ public Set> search(String searchTerm) { }); if (result.size() == 1) { - return result; + return null; } } @@ -145,7 +145,7 @@ public Set> search(String searchTerm) { result.add(hit.getSource()); }); - return result; + return null; } /** @@ -172,7 +172,7 @@ public Set> search(String searchTerm) { result.add(hit.getSource()); }); - return result; + return null; } @@ -299,7 +299,7 @@ public IndexOperationStatus removeFromIndex(String name) { // return new IndexOperationStatus(response.isFound(), name + " deleted from index"); } - public IndexOperationStatus bulkDeleteFromIndex(List entries) { + public IndexOperationStatus bulkRemoveByNameFromIndex(List entries) { return null; // if (entries.size() == 0) { // return new IndexOperationStatus(false, "Cannot index an empty list"); @@ -329,6 +329,11 @@ public IndexOperationStatus bulkDeleteFromIndex(List entries) { // + String.join(",", entries)); } + @Override + public IndexOperationStatus bulkRemoveFromIndex(List nameEntries) { + return null; + } + //=====================================Helpers=========================================================// diff --git a/model/src/main/java/org/oruko/dictionary/model/repository/NameEntryRepository.java b/model/src/main/java/org/oruko/dictionary/model/repository/NameEntryRepository.java index b5a6dbf2..0b127df4 100644 --- a/model/src/main/java/org/oruko/dictionary/model/repository/NameEntryRepository.java +++ b/model/src/main/java/org/oruko/dictionary/model/repository/NameEntryRepository.java @@ -37,10 +37,14 @@ public interface NameEntryRepository extends JpaRepository { * @return list of {@link NameEntry} */ List findByState(State state); - - // Search related repository access Set findByNameStartingWithAndState(String alphabet, State state); - NameEntry findByNameAndState(String alphabet, State state); + List findNameEntryByNameContainingAndState(String name, State state); + List findNameEntryByVariantsContainingAndState(String name, State state); + List findNameEntryByMeaningContainingAndState(String name, State state); + List findNameEntryByExtendedMeaningContainingAndState(String name, State state); + NameEntry findByNameAndState(String name, State state); + + Integer countByState(State state); Boolean deleteByNameAndState(String name, State state); } diff --git a/model/src/main/java/org/oruko/dictionary/service/JpaSearchService.java b/model/src/main/java/org/oruko/dictionary/service/JpaSearchService.java index 6765d5eb..4d9fbc4b 100644 --- a/model/src/main/java/org/oruko/dictionary/service/JpaSearchService.java +++ b/model/src/main/java/org/oruko/dictionary/service/JpaSearchService.java @@ -6,9 +6,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; @Service public class JpaSearchService implements SearchService { @@ -25,7 +27,8 @@ public NameEntry getByName(String nameQuery) { } @Override - public Set> search(String searchTerm) { + public Set search(String searchTerm) { + Set possibleFound = new LinkedHashSet<>(); /** * The following approach should be taken: * @@ -39,7 +42,21 @@ public Set> search(String searchTerm) { * 6. Do a full text search against meaning. Irrespective of outcome, proceed to 7 * 7. Do a full text search against extendedMeaning */ - return null; + NameEntry exactFound = nameEntryRepository.findByNameAndState(searchTerm, State.PUBLISHED); + if (exactFound != null) { + return Collections.singleton(exactFound); + } + Set startingWithSearchTerm = nameEntryRepository.findByNameStartingWithAndState(searchTerm, State.PUBLISHED); + if (startingWithSearchTerm != null && startingWithSearchTerm.size() > 0) { + return startingWithSearchTerm; + } + + possibleFound.addAll(nameEntryRepository.findNameEntryByNameContainingAndState(searchTerm, State.PUBLISHED)); + possibleFound.addAll(nameEntryRepository.findNameEntryByVariantsContainingAndState(searchTerm, State.PUBLISHED)); + possibleFound.addAll(nameEntryRepository.findNameEntryByMeaningContainingAndState(searchTerm, State.PUBLISHED)); + possibleFound.addAll(nameEntryRepository.findNameEntryByExtendedMeaningContainingAndState(searchTerm, State.PUBLISHED)); + + return possibleFound; } @Override @@ -49,8 +66,8 @@ public Set listByAlphabet(String alphabetQuery) { @Override public List autocomplete(String query) { - // TODO implement, disregarding the accents - return null; + Set names = nameEntryRepository.findByNameStartingWithAndState(query, State.PUBLISHED); + return names.stream().map(NameEntry::getName).collect(Collectors.toList()); } @Override @@ -60,7 +77,12 @@ public Integer getSearchableNames() { @Override public IndexOperationStatus bulkIndexName(List entries) { - return null; + if (entries.size() == 0) { + return new IndexOperationStatus(false, "Cannot index an empty list"); + } + nameEntryRepository.save(entries); + return new IndexOperationStatus(true, "Bulk indexing successfully. Indexed the following names " + + String.join(",", entries.stream().map(NameEntry::getName).collect(Collectors.toList()))); } public IndexOperationStatus removeFromIndex(String name) { @@ -74,7 +96,34 @@ public IndexOperationStatus removeFromIndex(String name) { } @Override - public IndexOperationStatus bulkDeleteFromIndex(List name) { - return null; + public IndexOperationStatus bulkRemoveByNameFromIndex(List names) { + if (names.size() == 0) { + return new IndexOperationStatus(false, "Cannot index an empty list"); + } + List nameEntries = names.stream().map(name -> nameEntryRepository.findByNameAndState(name, State.PUBLISHED)) + .collect(Collectors.toList()); + + + List namesUnpublished = nameEntries.stream().map(name -> { + name.setState(State.UNPUBLISHED); + return name; + }).collect(Collectors.toList()); + + nameEntryRepository.save(namesUnpublished); + return new IndexOperationStatus(true, "Successfully. " + + "Removed the following names from search index " + + String.join(",", names)); + } + + @Override + public IndexOperationStatus bulkRemoveFromIndex(List nameEntries) { + List namesUnpublished = nameEntries.stream() + .peek(name -> name.setState(State.UNPUBLISHED)) + .collect(Collectors.toList()); + + nameEntryRepository.save(namesUnpublished); + return new IndexOperationStatus(true, "Successfully. " + + "Removed the following names from search index " + + String.join(",", nameEntries.stream().map(NameEntry::getName).collect(Collectors.toList()))); } } diff --git a/model/src/main/java/org/oruko/dictionary/service/SearchService.java b/model/src/main/java/org/oruko/dictionary/service/SearchService.java index 007ed3e9..e4d7e1c2 100644 --- a/model/src/main/java/org/oruko/dictionary/service/SearchService.java +++ b/model/src/main/java/org/oruko/dictionary/service/SearchService.java @@ -3,7 +3,6 @@ import org.oruko.dictionary.model.NameEntry; import java.util.List; -import java.util.Map; import java.util.Set; public interface SearchService { @@ -23,7 +22,7 @@ public interface SearchService { * @param searchTerm the search term * @return the list of entries found */ - Set> search(String searchTerm); + Set search(String searchTerm); /** * Return all the names which starts with the given alphabet @@ -46,5 +45,6 @@ public interface SearchService { IndexOperationStatus bulkIndexName(List entries); IndexOperationStatus removeFromIndex(String name); - IndexOperationStatus bulkDeleteFromIndex(List name); + IndexOperationStatus bulkRemoveByNameFromIndex(List name); + IndexOperationStatus bulkRemoveFromIndex(List nameEntries); } diff --git a/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java b/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java index ed4eb1b0..53b1696b 100644 --- a/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java +++ b/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java @@ -95,22 +95,22 @@ public ResponseEntity> getMetaData() { */ @RequestMapping(value = {"/", ""}, method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) - public Set> search(@RequestParam(value = "q", required = true) String searchTerm, - HttpServletRequest request) { + public Set search(@RequestParam(value = "q", required = true) String searchTerm, + HttpServletRequest request) { - Set> name = searchService.search(searchTerm); - if (name != null - && name.size() == 1 - && name.stream().allMatch(result -> result.get("name").equals(searchTerm))) { + Set foundNames = searchService.search(searchTerm); + if (foundNames != null + && foundNames.size() == 1 + && foundNames.stream().allMatch(result -> result.getName().equals(searchTerm))) { eventPubService.publish(new NameSearchedEvent(searchTerm, request.getRemoteAddr())); } - return name; + return foundNames; } @RequestMapping(value = "/autocomplete", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public List getAutocomplete(@RequestParam(value = "q") Optional searchQuery) { - if (!searchQuery.isPresent()) { + if (!searchQuery.isPresent() || searchQuery.get().length() <= 2) { return Collections.emptyList(); } @@ -335,7 +335,7 @@ public ResponseEntity> batchDeleteFromIndex(@RequestBody Str return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); } - IndexOperationStatus indexOperationStatus = searchService.bulkDeleteFromIndex(found); + IndexOperationStatus indexOperationStatus = searchService.bulkRemoveFromIndex(nameEntries); updateIsIndexFlag(nameEntries, false, indexOperationStatus); return returnStatusMessage(notFound, indexOperationStatus); } diff --git a/website/src/main/java/org/oruko/dictionary/website/SearchResultController.java b/website/src/main/java/org/oruko/dictionary/website/SearchResultController.java index 910ccd86..9467c2b2 100644 --- a/website/src/main/java/org/oruko/dictionary/website/SearchResultController.java +++ b/website/src/main/java/org/oruko/dictionary/website/SearchResultController.java @@ -86,7 +86,6 @@ public String searchNameQuery(@RequestParam(value = "q",required = false) String return "redirect:/entries/"+nameQuery; } - Collections.reverse(names); map.addAttribute("query", nameQuery); map.addAttribute("names", names); From fdbb54123770718b8c1dad6f61b9918d900da092 Mon Sep 17 00:00:00 2001 From: Dadepo Aderemi Date: Sat, 2 Dec 2017 08:30:54 +0100 Subject: [PATCH 08/12] splitted the search into modules: search-api and two implementations --- bootstrap/pom.xml | 2 +- elasticsearch/pom.xml | 5 + ...ervicex.java => ElasticSearchService.java} | 347 +++++++++--------- jpasearch/pom.xml | 24 ++ .../search/jpa}/JpaSearchService.java | 4 +- pom.xml | 2 + searchapi/pom.xml | 26 ++ .../search/api}/IndexOperationStatus.java | 2 +- .../dictionary/search/api}/SearchService.java | 2 +- webapi/pom.xml | 2 +- .../web/event/NameDeletedEventHandler.java | 3 +- .../oruko/dictionary/web/rest/SearchApi.java | 18 +- .../dictionary/web/rest/SearchApiTest.java | 11 +- website/pom.xml | 6 + 14 files changed, 268 insertions(+), 186 deletions(-) rename elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/{ElasticSearchServicex.java => ElasticSearchService.java} (53%) create mode 100644 jpasearch/pom.xml rename {model/src/main/java/org/oruko/dictionary/service => jpasearch/src/main/java/org/oruko/dictionary/search/jpa}/JpaSearchService.java (97%) create mode 100644 searchapi/pom.xml rename {model/src/main/java/org/oruko/dictionary/service => searchapi/src/main/java/org/oruko/dictionary/search/api}/IndexOperationStatus.java (91%) rename {model/src/main/java/org/oruko/dictionary/service => searchapi/src/main/java/org/oruko/dictionary/search/api}/SearchService.java (97%) diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index a6ae241a..d62a583a 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -21,7 +21,7 @@ ${project.groupId} - elasticsearch-module + search-api 0.0.1-SNAPSHOT diff --git a/elasticsearch/pom.xml b/elasticsearch/pom.xml index d5391ee3..9216d452 100644 --- a/elasticsearch/pom.xml +++ b/elasticsearch/pom.xml @@ -12,6 +12,11 @@ elasticsearch-module + + ${project.groupId} + search-api + ${project.version} + org.apache.lucene diff --git a/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchServicex.java b/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchService.java similarity index 53% rename from elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchServicex.java rename to elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchService.java index 6c98cc92..edfaa9cd 100644 --- a/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchServicex.java +++ b/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchService.java @@ -1,9 +1,14 @@ package org.oruko.dictionary.elasticsearch; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.elasticsearch.action.admin.indices.create.CreateIndexResponse; +import org.elasticsearch.action.bulk.BulkRequestBuilder; +import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.count.CountResponse; +import org.elasticsearch.action.delete.DeleteRequestBuilder; import org.elasticsearch.action.delete.DeleteResponse; +import org.elasticsearch.action.index.IndexRequestBuilder; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.client.Client; import org.elasticsearch.index.query.FilterBuilders; @@ -12,8 +17,8 @@ import org.elasticsearch.node.Node; import org.elasticsearch.search.SearchHit; import org.oruko.dictionary.model.NameEntry; -import org.oruko.dictionary.service.IndexOperationStatus; -import org.oruko.dictionary.service.SearchService; +import org.oruko.dictionary.search.api.IndexOperationStatus; +import org.oruko.dictionary.search.api.SearchService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -21,6 +26,7 @@ import org.springframework.core.io.ResourceLoader; import org.springframework.stereotype.Service; import org.springframework.util.FileCopyUtils; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; import javax.annotation.PostConstruct; import java.io.IOException; @@ -31,6 +37,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import java.util.stream.Stream; import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery; @@ -41,9 +48,9 @@ * @author Dadepo Aderemi. */ @Service -public class ElasticSearchServicex implements SearchService { +public class ElasticSearchService implements SearchService { - private Logger logger = LoggerFactory.getLogger(ElasticSearchServicex.class); + private Logger logger = LoggerFactory.getLogger(ElasticSearchService.class); private Node node; private Client client; @@ -52,13 +59,13 @@ public class ElasticSearchServicex implements SearchService { private ObjectMapper mapper = new ObjectMapper(); /** - * Public constructor for {@link ElasticSearchServicex} + * Public constructor for {@link ElasticSearchService} * * @param node the elastic search Node * @param esConfig the elastic search config */ @Autowired - public ElasticSearchServicex(Node node, ESConfig esConfig) { + public ElasticSearchService(Node node, ESConfig esConfig) { this.node = node; this.client = node.client(); this.esConfig = esConfig; @@ -69,7 +76,11 @@ public void setResourceLoader(ResourceLoader loader) { this.resourceLoader = loader; } - ElasticSearchServicex() { + ElasticSearchService() { + } + + public boolean isElasticSearchNodeAvailable() { + return !node.isClosed(); } /** @@ -80,15 +91,25 @@ public void setResourceLoader(ResourceLoader loader) { */ @Override public NameEntry getByName(String nameQuery) { - return null; -// SearchResponse searchResponse = exactSearchByName(nameQuery); -// -// SearchHit[] hits = searchResponse.getHits().getHits(); -// if (hits.length == 1) { -// return hits[0].getSource(); -// } else { -// return null; -// } + SearchResponse searchResponse = exactSearchByName(nameQuery); + + SearchHit[] hits = searchResponse.getHits().getHits(); + if (hits.length == 1) { + return sourceToNameEntry(hits[0].getSource()); + } else { + return null; + } + } + + private NameEntry sourceToNameEntry(Map source) { + String valueAsString = null; + try { + valueAsString = mapper.writeValueAsString(source); + return mapper.readValue(valueAsString, NameEntry.class); + } catch (IOException e) { + e.printStackTrace(); + return null; + } } /** @@ -111,17 +132,17 @@ public Set search(String searchTerm) { * 7. Do a full text search against extendedMeaning */ - final Set> result = new LinkedHashSet<>(); + final Set result = new LinkedHashSet<>(); // 1. exact search SearchResponse searchResponse = exactSearchByName(searchTerm); if (searchResponse.getHits().getHits().length >= 1) { Stream.of(searchResponse.getHits().getHits()).forEach(hit -> { - result.add(hit.getSource()); + result.add(sourceToNameEntry(hit.getSource())); }); if (result.size() == 1) { - return null; + return result; } } @@ -129,11 +150,11 @@ public Set search(String searchTerm) { searchResponse = exactSearchByNameAsciiFolded(searchTerm); if (searchResponse.getHits().getHits().length >= 1) { Stream.of(searchResponse.getHits().getHits()).forEach(hit -> { - result.add(hit.getSource()); + result.add(sourceToNameEntry(hit.getSource())); }); if (result.size() == 1) { - return null; + return result; } } @@ -142,10 +163,10 @@ public Set search(String searchTerm) { searchResponse = prefixFilterSearch(searchTerm, false); if (searchResponse.getHits().getHits().length >= 1) { Stream.of(searchResponse.getHits().getHits()).forEach(hit -> { - result.add(hit.getSource()); + result.add(sourceToNameEntry(hit.getSource())); }); - return null; + return result; } /** @@ -157,28 +178,28 @@ public Set search(String searchTerm) { * TODO Should revisit */ MultiMatchQueryBuilder searchSpec = QueryBuilders.multiMatchQuery(searchTerm, - "name.autocomplete", - "meaning", - "extendedMeaning", - "variants"); + "name.autocomplete", + "meaning", + "extendedMeaning", + "variants"); SearchResponse tempSearchAll = client.prepareSearch(esConfig.getIndexName()) - .setQuery(searchSpec) - .setSize(20) - .execute() - .actionGet(); + .setQuery(searchSpec) + .setSize(20) + .execute() + .actionGet(); Stream.of(tempSearchAll.getHits().getHits()).forEach(hit -> { - result.add(hit.getSource()); + result.add(sourceToNameEntry(hit.getSource())); }); - return null; + return result; } @Override public Set listByAlphabet(String alphabetQuery) { - final Set> result = new LinkedHashSet<>(); + final Set result = new LinkedHashSet<>(); final SearchResponse searchResponse = prefixFilterSearch(alphabetQuery, true); final SearchHit[] hits = searchResponse.getHits().getHits(); @@ -186,10 +207,10 @@ public Set listByAlphabet(String alphabetQuery) { Collections.reverse(searchHits); searchHits.forEach(hit -> { - result.add(hit.getSource()); + result.add(sourceToNameEntry(hit.getSource())); }); - return null; + return result; } /** @@ -213,10 +234,17 @@ public List autocomplete(String query) { @Override public Integer getSearchableNames() { - return null; + try { + CountResponse response = client.prepareCount(esConfig.getIndexName()) + .setQuery(matchAllQuery()) + .execute() + .actionGet(); + return Math.toIntExact(response.getCount()); + } catch (Exception e) { + return 0; + } } - /** * Add a {@link org.oruko.dictionary.model.NameEntry} into ElasticSearch index * @@ -224,61 +252,62 @@ public Integer getSearchableNames() { * @return returns true | false depending on if the indexing operation was successful. */ public IndexOperationStatus indexName(NameEntry entry) { - return null; -// if (!isElasticSearchNodeAvailable()) { -// return new IndexOperationStatus(false, -// "Index attempt unsuccessful. You do not have an elasticsearch node running"); -// } -// -// try { -// String entryAsJson = mapper.writeValueAsString(entry); -// String name = entry.getName(); -// client.prepareIndex(esConfig.getIndexName(), esConfig.getDocumentType(), name.toLowerCase()) -// .setSource(entryAsJson) -// .execute() -// .actionGet(); -// -// return new IndexOperationStatus(true, name + " indexed successfully"); -// } catch (JsonProcessingException e) { -// logger.info("Failed to parse NameEntry into Json", e); -// return new IndexOperationStatus(true, "Failed to parse NameEntry into Json"); -// } + + if (!isElasticSearchNodeAvailable()) { + return new IndexOperationStatus(false, + "Index attempt unsuccessful. You do not have an elasticsearch node running"); + } + + try { + String entryAsJson = mapper.writeValueAsString(entry); + String name = entry.getName(); + client.prepareIndex(esConfig.getIndexName(), esConfig.getDocumentType(), name.toLowerCase()) + .setSource(entryAsJson) + .execute() + .actionGet(); + + return new IndexOperationStatus(true, name + " indexed successfully"); + } catch (JsonProcessingException e) { + logger.info("Failed to parse NameEntry into Json", e); + return new IndexOperationStatus(true, "Failed to parse NameEntry into Json"); + } } + @Override public IndexOperationStatus bulkIndexName(List entries) { - return null; -// if (entries.size() == 0) { -// return new IndexOperationStatus(false, "Cannot index an empty list"); -// } -// -// if (!isElasticSearchNodeAvailable()) { -// return new IndexOperationStatus(false, -// "Index attempt unsuccessful. You do not have an elasticsearch node running"); -// } -// -// BulkRequestBuilder bulkRequest = client.prepareBulk(); -// entries.forEach(entry -> { -// try { -// String entryAsJson = mapper.writeValueAsString(entry); -// String name = entry.getName(); -// IndexRequestBuilder request = client.prepareIndex(esConfig.getIndexName(), -// esConfig.getDocumentType(), -// name.toLowerCase()) -// .setSource(entryAsJson); -// bulkRequest.add(request); -// } catch (JsonProcessingException e) { -// logger.debug("Error while building bulk indexing operation", e); -// } -// }); -// -// BulkResponse bulkResponse = bulkRequest.execute().actionGet(); -// if (bulkResponse.hasFailures()) { -// return new IndexOperationStatus(false, bulkResponse.buildFailureMessage()); -// } -// -// return new IndexOperationStatus(true, "Bulk indexing successfully. Indexed the following names " -// + String.join(",", entries.stream().map(entry -> entry.getName()).collect(Collectors.toList()))); + + if (entries.size() == 0) { + return new IndexOperationStatus(false, "Cannot index an empty list"); + } + + if (!isElasticSearchNodeAvailable()) { + return new IndexOperationStatus(false, + "Index attempt unsuccessful. You do not have an elasticsearch node running"); + } + + BulkRequestBuilder bulkRequest = client.prepareBulk(); + entries.forEach(entry -> { + try { + String entryAsJson = mapper.writeValueAsString(entry); + String name = entry.getName(); + IndexRequestBuilder request = client.prepareIndex(esConfig.getIndexName(), + esConfig.getDocumentType(), + name.toLowerCase()) + .setSource(entryAsJson); + bulkRequest.add(request); + } catch (JsonProcessingException e) { + logger.debug("Error while building bulk indexing operation", e); + } + }); + + BulkResponse bulkResponse = bulkRequest.execute().actionGet(); + if (bulkResponse.hasFailures()) { + return new IndexOperationStatus(false, bulkResponse.buildFailureMessage()); + } + + return new IndexOperationStatus(true, "Bulk indexing successfully. Indexed the following names " + + String.join(",", entries.stream().map(entry -> entry.getName()).collect(Collectors.toList()))); } /** @@ -288,50 +317,44 @@ public IndexOperationStatus bulkIndexName(List entries) { * @return true | false depending on if deleting operation was successful */ public IndexOperationStatus removeFromIndex(String name) { - return null; -// if (!isElasticSearchNodeAvailable()) { -// -// return new IndexOperationStatus(false, -// "Delete unsuccessful. You do not have an elasticsearch node running"); -// } -// -// DeleteResponse response = deleteName(name); -// return new IndexOperationStatus(response.isFound(), name + " deleted from index"); + + if (!isElasticSearchNodeAvailable()) { + + return new IndexOperationStatus(false, + "Delete unsuccessful. You do not have an elasticsearch node running"); + } + + DeleteResponse response = deleteName(name); + return new IndexOperationStatus(response.isFound(), name + " deleted from index"); } public IndexOperationStatus bulkRemoveByNameFromIndex(List entries) { - return null; -// if (entries.size() == 0) { -// return new IndexOperationStatus(false, "Cannot index an empty list"); -// } -// -// if (!isElasticSearchNodeAvailable()) { -// return new IndexOperationStatus(false, -// "Delete unsuccessful. You do not have an elasticsearch node running"); -// } -// -// BulkRequestBuilder bulkRequest = client.prepareBulk(); -// -// entries.forEach(entry -> { -// DeleteRequestBuilder request = client.prepareDelete(esConfig.getIndexName(), -// esConfig.getDocumentType(), -// entry.toLowerCase()); -// bulkRequest.add(request); -// }); -// -// BulkResponse bulkResponse = bulkRequest.execute().actionGet(); -// if (bulkResponse.hasFailures()) { -// return new IndexOperationStatus(false, bulkResponse.buildFailureMessage()); -// } -// -// return new IndexOperationStatus(true, "Bulk deleting successfully. " -// + "Removed the following names from search index " -// + String.join(",", entries)); - } + if (entries.size() == 0) { + return new IndexOperationStatus(false, "Cannot index an empty list"); + } - @Override - public IndexOperationStatus bulkRemoveFromIndex(List nameEntries) { - return null; + if (!isElasticSearchNodeAvailable()) { + return new IndexOperationStatus(false, + "Delete unsuccessful. You do not have an elasticsearch node running"); + } + + BulkRequestBuilder bulkRequest = client.prepareBulk(); + + entries.forEach(entry -> { + DeleteRequestBuilder request = client.prepareDelete(esConfig.getIndexName(), + esConfig.getDocumentType(), + entry.toLowerCase()); + bulkRequest.add(request); + }); + + BulkResponse bulkResponse = bulkRequest.execute().actionGet(); + if (bulkResponse.hasFailures()) { + return new IndexOperationStatus(false, bulkResponse.buildFailureMessage()); + } + + return new IndexOperationStatus(true, "Bulk deleting successfully. " + + "Removed the following names from search index " + + String.join(",", entries)); } @@ -347,9 +370,9 @@ private DeleteResponse deleteName(String name) { //TODO revisit. Omo returns Omowunmi and Owolabi. Ideal this should return just one result private SearchResponse exactSearchByName(String nameQuery) { return client.prepareSearch(esConfig.getIndexName()) - .setPostFilter(FilterBuilders.termFilter("name", nameQuery.toLowerCase())) - .execute() - .actionGet(); + .setPostFilter(FilterBuilders.termFilter("name", nameQuery.toLowerCase())) + .execute() + .actionGet(); } private SearchResponse prefixFilterSearch(String nameQuery, boolean getAll) { @@ -361,26 +384,26 @@ private SearchResponse prefixFilterSearch(String nameQuery, boolean getAll) { } return client.prepareSearch(esConfig.getIndexName()) - .setPostFilter(FilterBuilders.prefixFilter("name", nameQuery.toLowerCase())) - .setSize(resultSet) - .execute() - .actionGet(); + .setPostFilter(FilterBuilders.prefixFilter("name", nameQuery.toLowerCase())) + .setSize(resultSet) + .execute() + .actionGet(); } private SearchResponse exactSearchByNameAsciiFolded(String nameQuery) { return client.prepareSearch(esConfig.getIndexName()) - .setQuery(QueryBuilders.matchQuery("name.asciifolded", nameQuery.toLowerCase())) - .setSize(20) - .execute() - .actionGet(); + .setQuery(QueryBuilders.matchQuery("name.asciifolded", nameQuery.toLowerCase())) + .setSize(20) + .execute() + .actionGet(); } private SearchResponse partialSearchByName(String nameQuery) { return client.prepareSearch(esConfig.getIndexName()) - .setQuery(QueryBuilders.matchQuery("name.autocomplete", nameQuery.toLowerCase())) - .setSize(20) - .execute() - .actionGet(); + .setQuery(QueryBuilders.matchQuery("name.autocomplete", nameQuery.toLowerCase())) + .setSize(20) + .execute() + .actionGet(); } // On start up, creates an index for the application if one does not @@ -402,38 +425,32 @@ private void buildElasticSearchClient() { try { boolean exists = this.client.admin().indices() - .prepareExists(esConfig.getIndexName()) - .execute() - .actionGet() - .isExists(); + .prepareExists(esConfig.getIndexName()) + .execute() + .actionGet() + .isExists(); if (!exists) { CreateIndexResponse createIndexResponse = this.client.admin().indices() - .prepareCreate(esConfig.getIndexName()) - .setSettings(indexSettings) - .addMapping(esConfig.getDocumentType(), - mapping).execute() - .actionGet(); + .prepareCreate(esConfig.getIndexName()) + .setSettings(indexSettings) + .addMapping(esConfig.getDocumentType(), + mapping).execute() + .actionGet(); logger.info("Created {} and added {} to type {} status was {} at startup", - esConfig.getIndexName(), mapping, esConfig.getDocumentType(), - createIndexResponse.isAcknowledged()); + esConfig.getIndexName(), mapping, esConfig.getDocumentType(), + createIndexResponse.isAcknowledged()); } } catch (Exception e) { logger.error("ElasticSearch not running", e); } } - public long getCount() { - try { - CountResponse response = client.prepareCount(esConfig.getIndexName()) - .setQuery(matchAllQuery()) - .execute() - .actionGet(); - return response.getCount(); - } catch (Exception e) { - return 0; - } + + @Override + public IndexOperationStatus bulkRemoveFromIndex(List nameEntries) { + throw new NotImplementedException(); } } diff --git a/jpasearch/pom.xml b/jpasearch/pom.xml new file mode 100644 index 00000000..22c23ef7 --- /dev/null +++ b/jpasearch/pom.xml @@ -0,0 +1,24 @@ + + + + dictionary + org.yorubaname + 0.0.1-SNAPSHOT + + 4.0.0 + + jpa-search + + + + + + ${project.groupId} + search-api + ${project.version} + + + + \ No newline at end of file diff --git a/model/src/main/java/org/oruko/dictionary/service/JpaSearchService.java b/jpasearch/src/main/java/org/oruko/dictionary/search/jpa/JpaSearchService.java similarity index 97% rename from model/src/main/java/org/oruko/dictionary/service/JpaSearchService.java rename to jpasearch/src/main/java/org/oruko/dictionary/search/jpa/JpaSearchService.java index 4d9fbc4b..4662bd71 100644 --- a/model/src/main/java/org/oruko/dictionary/service/JpaSearchService.java +++ b/jpasearch/src/main/java/org/oruko/dictionary/search/jpa/JpaSearchService.java @@ -1,8 +1,10 @@ -package org.oruko.dictionary.service; +package org.oruko.dictionary.search.jpa; import org.oruko.dictionary.model.NameEntry; import org.oruko.dictionary.model.State; import org.oruko.dictionary.model.repository.NameEntryRepository; +import org.oruko.dictionary.search.api.IndexOperationStatus; +import org.oruko.dictionary.search.api.SearchService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; diff --git a/pom.xml b/pom.xml index b2ffca69..4302f001 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,8 @@ elasticsearch website event + searchapi + jpasearch pom diff --git a/searchapi/pom.xml b/searchapi/pom.xml new file mode 100644 index 00000000..a3216dc8 --- /dev/null +++ b/searchapi/pom.xml @@ -0,0 +1,26 @@ + + + + dictionary + org.yorubaname + 0.0.1-SNAPSHOT + + 4.0.0 + + search-api + + + + + + + ${project.groupId} + model-module + ${project.version} + + + + + \ No newline at end of file diff --git a/model/src/main/java/org/oruko/dictionary/service/IndexOperationStatus.java b/searchapi/src/main/java/org/oruko/dictionary/search/api/IndexOperationStatus.java similarity index 91% rename from model/src/main/java/org/oruko/dictionary/service/IndexOperationStatus.java rename to searchapi/src/main/java/org/oruko/dictionary/search/api/IndexOperationStatus.java index 9489da6c..747f5a34 100644 --- a/model/src/main/java/org/oruko/dictionary/service/IndexOperationStatus.java +++ b/searchapi/src/main/java/org/oruko/dictionary/search/api/IndexOperationStatus.java @@ -1,4 +1,4 @@ -package org.oruko.dictionary.service; +package org.oruko.dictionary.search.api; /** * For communicating the status of operation on the index diff --git a/model/src/main/java/org/oruko/dictionary/service/SearchService.java b/searchapi/src/main/java/org/oruko/dictionary/search/api/SearchService.java similarity index 97% rename from model/src/main/java/org/oruko/dictionary/service/SearchService.java rename to searchapi/src/main/java/org/oruko/dictionary/search/api/SearchService.java index e4d7e1c2..826f247c 100644 --- a/model/src/main/java/org/oruko/dictionary/service/SearchService.java +++ b/searchapi/src/main/java/org/oruko/dictionary/search/api/SearchService.java @@ -1,4 +1,4 @@ -package org.oruko.dictionary.service; +package org.oruko.dictionary.search.api; import org.oruko.dictionary.model.NameEntry; diff --git a/webapi/pom.xml b/webapi/pom.xml index 98d44bbe..2726dfb1 100644 --- a/webapi/pom.xml +++ b/webapi/pom.xml @@ -22,7 +22,7 @@ ${project.groupId} - elasticsearch-module + search-api ${project.version} diff --git a/webapi/src/main/java/org/oruko/dictionary/web/event/NameDeletedEventHandler.java b/webapi/src/main/java/org/oruko/dictionary/web/event/NameDeletedEventHandler.java index aaa5bfb6..585ef5fc 100644 --- a/webapi/src/main/java/org/oruko/dictionary/web/event/NameDeletedEventHandler.java +++ b/webapi/src/main/java/org/oruko/dictionary/web/event/NameDeletedEventHandler.java @@ -3,7 +3,7 @@ import com.google.common.eventbus.AllowConcurrentEvents; import com.google.common.eventbus.Subscribe; import org.oruko.dictionary.events.NameDeletedEvent; -import org.oruko.dictionary.service.SearchService; +import org.oruko.dictionary.search.api.SearchService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; @@ -15,6 +15,7 @@ @Component public class NameDeletedEventHandler { + // TODO should not be hardwiring a bean here @Qualifier("jpaSearchService") @Autowired SearchService nameSearchService; diff --git a/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java b/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java index 53b1696b..7ebe3e6a 100644 --- a/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java +++ b/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java @@ -5,8 +5,8 @@ import org.oruko.dictionary.events.NameSearchedEvent; import org.oruko.dictionary.model.NameEntry; import org.oruko.dictionary.model.State; -import org.oruko.dictionary.service.IndexOperationStatus; -import org.oruko.dictionary.service.JpaSearchService; +import org.oruko.dictionary.search.api.IndexOperationStatus; +import org.oruko.dictionary.search.api.SearchService; import org.oruko.dictionary.web.NameEntryService; import org.oruko.dictionary.web.event.RecentIndexes; import org.oruko.dictionary.web.event.RecentSearches; @@ -50,7 +50,7 @@ public class SearchApi { private Logger logger = LoggerFactory.getLogger(SearchApi.class); private NameEntryService nameEntryService; - private JpaSearchService searchService; + private SearchService searchService; private RecentSearches recentSearches; private RecentIndexes recentIndexes; private EventPubService eventPubService; @@ -65,7 +65,7 @@ public class SearchApi { @Autowired public SearchApi(EventPubService eventPubService, NameEntryService nameEntryService, - JpaSearchService searchService, + SearchService searchService, RecentSearches recentSearches, RecentIndexes recentIndexes) { this.eventPubService = eventPubService; @@ -178,7 +178,7 @@ public Map allActivity() { * Endpoint to index a NameEntry sent in as JSON string. * * @param entry the {@link NameEntry} representation of the JSON String. - * @return a {@link org.springframework.http.ResponseEntity} representing the status of the operation. + * @return a {@link ResponseEntity} representing the status of the operation. */ @RequestMapping(value = "/indexes", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, @@ -207,7 +207,7 @@ private void publishNameIsIndexed(NameEntry nameEntry) { * Endpoint that takes a name, looks it up in the repository and index the entry found * * @param name the name - * @return a {@link org.springframework.http.ResponseEntity} representing the status of the operation + * @return a {@link ResponseEntity} representing the status of the operation */ @RequestMapping(value = "/indexes/{name}", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, @@ -238,7 +238,7 @@ public ResponseEntity> indexEntryByName(@PathVariable String * It allows for batch indexing of names * * @param names the array of names - * @return a {@link org.springframework.http.ResponseEntity} representing the status of the operation + * @return a {@link ResponseEntity} representing the status of the operation */ @RequestMapping(value = "/indexes/batch", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, @@ -281,7 +281,7 @@ public ResponseEntity> batchIndexEntriesByName(@RequestBody * Endpoint used to remove a name from the index. * * @param name the name to remove from the index. - * @return a {@link org.springframework.http.ResponseEntity} representing the status of the operation. + * @return a {@link ResponseEntity} representing the status of the operation. */ @RequestMapping(value = "/indexes/{name}", method = RequestMethod.DELETE, consumes = MediaType.APPLICATION_JSON_VALUE, @@ -309,7 +309,7 @@ public ResponseEntity> deleteFromIndex(@PathVariable String * Endpoint used to remove a list of names from the index. * * @param names the names to remove from the index. - * @return a {@link org.springframework.http.ResponseEntity} representing the status of the operation. + * @return a {@link ResponseEntity} representing the status of the operation. */ @RequestMapping(value = "/indexes/batch", method = RequestMethod.DELETE, consumes = MediaType.APPLICATION_JSON_VALUE, diff --git a/webapi/src/test/java/org/oruko/dictionary/web/rest/SearchApiTest.java b/webapi/src/test/java/org/oruko/dictionary/web/rest/SearchApiTest.java index c88a5331..d85754b5 100644 --- a/webapi/src/test/java/org/oruko/dictionary/web/rest/SearchApiTest.java +++ b/webapi/src/test/java/org/oruko/dictionary/web/rest/SearchApiTest.java @@ -7,7 +7,7 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.oruko.dictionary.events.EventPubService; -import org.oruko.dictionary.service.SearchService; +import org.oruko.dictionary.search.api.SearchService; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -76,11 +76,10 @@ public void test_auto_complete_with_empty_query() throws Exception { @Test public void testFindByName_NameNotFound() throws Exception { - when(searchService.getByName("query")).thenReturn(null); - mockMvc.perform(get("/v1/search/query")) - .andExpect(jsonPath("$").isEmpty()) - .andExpect(status().isOk()); + when(searchService.getByName("searchTerm")).thenReturn(null); + mockMvc.perform(get("/v1/search/searchTerm")) + .andExpect(content().string("")) + .andExpect(status().isOk()); } - } diff --git a/website/pom.xml b/website/pom.xml index 806bd570..7866cb4b 100644 --- a/website/pom.xml +++ b/website/pom.xml @@ -24,6 +24,12 @@ + + ${project.groupId} + jpa-search + ${project.version} + + ${project.groupId} bootstrap-module From ee64aa23d028fd2d0b870cb80606931771784f7f Mon Sep 17 00:00:00 2001 From: Dadepo Aderemi Date: Sat, 2 Dec 2017 18:35:21 +0100 Subject: [PATCH 09/12] Improved the autocomplete behaviour for JPA search. Also Removed isIndexed property from the NameEntry entity. Also updated readme --- README.md | 30 ++++++++++++++++ .../auth/util/ApiUsersDatabaseImporter.java | 2 +- .../oruko/dictionary/util/DataImporter.java | 4 +-- .../elasticsearch/ElasticSearchService.java | 5 ++- jpasearch/pom.xml | 2 +- .../search/jpa/JpaSearchService.java | 17 ++++++++-- .../dictionary/model/AbstractNameEntry.java | 12 ------- .../model/repository/NameEntryRepository.java | 8 ++--- .../dictionary/search/api/SearchService.java | 2 +- .../oruko/dictionary/web/rest/NameApi.java | 34 +++++-------------- .../oruko/dictionary/web/rest/SearchApi.java | 31 +++++------------ .../dictionary/web/rest/NameApiTest.java | 12 ------- website/pom.xml | 2 +- 13 files changed, 72 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index 5e1a436c..7528d578 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,36 @@ when starting the application. For example: Remember this command needs to be run from the website module, that is `{parent_directory}/website` directory. +### Search functionality + +The search API is defined in the `searchapi` module. We currently have two implementations for the search api: +1. ElasticSearch - Implemented in the `elasticsearch-module` module +2. JPA based search - Implemented in the `jpa-search-module` module + +The `jpa-search-module` module is used in the `website` module which represents the website running at www.yorubaname.com + +If you want to use `elasticsearch` module then remove the following section in the pom.xml for `website` module: + +``` + + ${project.groupId} + jpa-search-module + ${project.version} + +``` + +with + +``` + + ${project.groupId} + elasticsearch-module + ${project.version} + +``` + +The `elasticsearch-module` needs to be configured. This is explained in the next session. + ### Configuring ElasticSearch Properties The ElasticSearch module does not require the installation of ElasticSearch as it will run with an embedded ElasticSearch instance: diff --git a/auth/src/main/java/org/oruko/dictionary/auth/util/ApiUsersDatabaseImporter.java b/auth/src/main/java/org/oruko/dictionary/auth/util/ApiUsersDatabaseImporter.java index 306be2c7..50939ebf 100644 --- a/auth/src/main/java/org/oruko/dictionary/auth/util/ApiUsersDatabaseImporter.java +++ b/auth/src/main/java/org/oruko/dictionary/auth/util/ApiUsersDatabaseImporter.java @@ -40,7 +40,7 @@ public void initApiUsers() { /** * Only initialize the database only when in dev */ - if (!host.equalsIgnoreCase("localhost")) { + if (host.equalsIgnoreCase("localhost")) { // lexi user ApiUser lexicographer = new ApiUser(); diff --git a/bootstrap/src/main/java/org/oruko/dictionary/util/DataImporter.java b/bootstrap/src/main/java/org/oruko/dictionary/util/DataImporter.java index b7aeffb5..8e5a985a 100644 --- a/bootstrap/src/main/java/org/oruko/dictionary/util/DataImporter.java +++ b/bootstrap/src/main/java/org/oruko/dictionary/util/DataImporter.java @@ -43,7 +43,7 @@ public void initializeData() { * Only initialize the database only when in dev * //TODO move this to profiles */ - if (!host.equalsIgnoreCase("localhost")) { + if (host.equalsIgnoreCase("localhost")) { List nameEntries = initializeDb(); } } @@ -106,7 +106,6 @@ private List initializeDb() { bolanle.setExtendedMeaning("This is extended dummy meaning for Bọ́lánlé"); bolanle.setGeoLocation(Arrays.asList(new GeoLocation("IBADAN", "NWY"))); bolanle.setEtymology(etymology); - bolanle.setIndexed(true); bolanle.setState(State.PUBLISHED); @@ -115,7 +114,6 @@ private List initializeDb() { bimpe.setExtendedMeaning("This is extended dummy meaning for Bimpe"); bimpe.setGeoLocation(Arrays.asList(new GeoLocation("IBADAN", "NWY"))); bimpe.setEtymology(etymology); - bolanle.setIndexed(true); bimpe.setState(State.PUBLISHED); NameEntry ade0 = new NameEntry("Ade"); diff --git a/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchService.java b/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchService.java index edfaa9cd..53da937a 100644 --- a/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchService.java +++ b/elasticsearch/src/main/java/org/oruko/dictionary/elasticsearch/ElasticSearchService.java @@ -30,7 +30,6 @@ import javax.annotation.PostConstruct; import java.io.IOException; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; @@ -220,8 +219,8 @@ public Set listByAlphabet(String alphabetQuery) { * @return the list of partial matches */ @Override - public List autocomplete(String query) { - final List result = new ArrayList(); + public Set autocomplete(String query) { + final Set result = new LinkedHashSet<>(); SearchResponse tempSearchAll = partialSearchByName(query); diff --git a/jpasearch/pom.xml b/jpasearch/pom.xml index 22c23ef7..9835fb6f 100644 --- a/jpasearch/pom.xml +++ b/jpasearch/pom.xml @@ -9,7 +9,7 @@ 4.0.0 - jpa-search + jpa-search-module diff --git a/jpasearch/src/main/java/org/oruko/dictionary/search/jpa/JpaSearchService.java b/jpasearch/src/main/java/org/oruko/dictionary/search/jpa/JpaSearchService.java index 4662bd71..3e202cdd 100644 --- a/jpasearch/src/main/java/org/oruko/dictionary/search/jpa/JpaSearchService.java +++ b/jpasearch/src/main/java/org/oruko/dictionary/search/jpa/JpaSearchService.java @@ -67,9 +67,20 @@ public Set listByAlphabet(String alphabetQuery) { } @Override - public List autocomplete(String query) { - Set names = nameEntryRepository.findByNameStartingWithAndState(query, State.PUBLISHED); - return names.stream().map(NameEntry::getName).collect(Collectors.toList()); + public Set autocomplete(String query) { + Set names = new LinkedHashSet<>(); + Set nameToReturn = new LinkedHashSet<>(); + // TODO calling the db in a for loop might not be a terribly good idea. Revist + for (int i=2; i otherParts = nameEntryRepository.findNameEntryByNameContainingAndState(query, State.PUBLISHED); + names.addAll(otherParts); + names.forEach(name -> { + nameToReturn.add(name.getName()); + }); + return nameToReturn; } @Override diff --git a/model/src/main/java/org/oruko/dictionary/model/AbstractNameEntry.java b/model/src/main/java/org/oruko/dictionary/model/AbstractNameEntry.java index e519cad5..b23b2050 100644 --- a/model/src/main/java/org/oruko/dictionary/model/AbstractNameEntry.java +++ b/model/src/main/java/org/oruko/dictionary/model/AbstractNameEntry.java @@ -92,9 +92,6 @@ public abstract class AbstractNameEntry { @ElementCollection protected List etymology; - @Column - protected Boolean isIndexed = false; - @Column @Enumerated(EnumType.STRING) protected State state; @@ -253,15 +250,6 @@ public void setTags(String tags) { this.tags = tags; } - public Boolean getIndexed() { - return isIndexed; - } - - // REMOVE AND USE PUBLISHED STATE - public void setIndexed(Boolean published) { - this.isIndexed = published; - } - public LocalDateTime getUpdatedAt() { return updatedAt; } diff --git a/model/src/main/java/org/oruko/dictionary/model/repository/NameEntryRepository.java b/model/src/main/java/org/oruko/dictionary/model/repository/NameEntryRepository.java index 0b127df4..b92c9842 100644 --- a/model/src/main/java/org/oruko/dictionary/model/repository/NameEntryRepository.java +++ b/model/src/main/java/org/oruko/dictionary/model/repository/NameEntryRepository.java @@ -38,10 +38,10 @@ public interface NameEntryRepository extends JpaRepository { */ List findByState(State state); Set findByNameStartingWithAndState(String alphabet, State state); - List findNameEntryByNameContainingAndState(String name, State state); - List findNameEntryByVariantsContainingAndState(String name, State state); - List findNameEntryByMeaningContainingAndState(String name, State state); - List findNameEntryByExtendedMeaningContainingAndState(String name, State state); + Set findNameEntryByNameContainingAndState(String name, State state); + Set findNameEntryByVariantsContainingAndState(String name, State state); + Set findNameEntryByMeaningContainingAndState(String name, State state); + Set findNameEntryByExtendedMeaningContainingAndState(String name, State state); NameEntry findByNameAndState(String name, State state); diff --git a/searchapi/src/main/java/org/oruko/dictionary/search/api/SearchService.java b/searchapi/src/main/java/org/oruko/dictionary/search/api/SearchService.java index 826f247c..d98e564a 100644 --- a/searchapi/src/main/java/org/oruko/dictionary/search/api/SearchService.java +++ b/searchapi/src/main/java/org/oruko/dictionary/search/api/SearchService.java @@ -39,7 +39,7 @@ public interface SearchService { * @param query the query * @return the list of partial matches */ - List autocomplete(String query); + Set autocomplete(String query); Integer getSearchableNames(); diff --git a/webapi/src/main/java/org/oruko/dictionary/web/rest/NameApi.java b/webapi/src/main/java/org/oruko/dictionary/web/rest/NameApi.java index 8da81e65..a116f732 100644 --- a/webapi/src/main/java/org/oruko/dictionary/web/rest/NameApi.java +++ b/webapi/src/main/java/org/oruko/dictionary/web/rest/NameApi.java @@ -32,22 +32,22 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; +import javax.validation.Valid; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.UUID; -import java.util.Collections; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.validation.Valid; /** * End point for inserting and retrieving NameDto Entries @@ -151,21 +151,17 @@ public ResponseEntity> getMetaData() { * result set from. 0 if none is given * @param countParam a {@link Integer} the number of names to return. 50 is none is given * @return the list of {@link org.oruko.dictionary.model.NameEntry} - * @throws JsonProcessingException JSON processing exception */ @RequestMapping(value = "/v1/names", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public List getAllNames(@RequestParam("page") final Optional pageParam, @RequestParam("count") final Optional countParam, @RequestParam("all") final Optional all, @RequestParam("submittedBy") final Optional submittedBy, - @RequestParam("state") final Optional state, - @RequestParam(value = "indexed", required = false) final Optional indexed) - throws JsonProcessingException { + @RequestParam("state") final Optional state) { - List names = new ArrayList<>(); List allNameEntries; - if (all.isPresent() && all.get() == true) { + if (all.isPresent() && all.get()) { if (state.isPresent()) { allNameEntries = entryService.loadAllByState(state); } else { @@ -175,28 +171,14 @@ public List getAllNames(@RequestParam("page") final Optional allNameEntries = entryService.loadByState(state, pageParam, countParam); } - names.addAll(allNameEntries); - - // for filtering based on whether entry has been indexed - Predicate filterBasedOnIndex = (name) -> { - if (indexed.isPresent()) { - return name.getIndexed().equals(indexed.get()); - } else { - return true; - } - }; + List names = new ArrayList<>(allNameEntries); // for filtering based on value of submitBy - Predicate filterBasedOnSubmitBy = (name) -> { - if (submittedBy.isPresent()) { - return name.getSubmittedBy().trim().equalsIgnoreCase(submittedBy.get().trim()); - } else { - return true; - } - }; + Predicate filterBasedOnSubmitBy = (name) -> submittedBy + .map(s -> name.getSubmittedBy().trim().equalsIgnoreCase(s.trim())) + .orElse(true); return names.stream() - .filter(filterBasedOnIndex) .filter(filterBasedOnSubmitBy) .collect(Collectors.toCollection(ArrayList::new)); diff --git a/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java b/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java index 7ebe3e6a..f42e37e8 100644 --- a/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java +++ b/webapi/src/main/java/org/oruko/dictionary/web/rest/SearchApi.java @@ -36,7 +36,6 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; /** * Handler for search functionality @@ -109,9 +108,9 @@ public Set search(@RequestParam(value = "q", required = true) String @RequestMapping(value = "/autocomplete", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) - public List getAutocomplete(@RequestParam(value = "q") Optional searchQuery) { - if (!searchQuery.isPresent() || searchQuery.get().length() <= 2) { - return Collections.emptyList(); + public Set getAutocomplete(@RequestParam(value = "q") Optional searchQuery) { + if (!searchQuery.isPresent() || searchQuery.get().length() < 2) { + return Collections.emptySet(); } String query = searchQuery.get(); @@ -191,7 +190,6 @@ public ResponseEntity> indexEntry(@Valid NameEntry entry) { return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); } - nameEntry.setIndexed(true); nameEntry.setState(State.PUBLISHED); nameEntryService.saveName(nameEntry); publishNameIsIndexed(nameEntry); @@ -223,7 +221,6 @@ public ResponseEntity> indexEntryByName(@PathVariable String } publishNameIsIndexed(nameEntry); - nameEntry.setIndexed(true); nameEntry.setState(State.PUBLISHED); nameEntryService.saveName(nameEntry); response.put("message", name + " has been published"); @@ -264,7 +261,11 @@ public ResponseEntity> batchIndexEntriesByName(@RequestBody } IndexOperationStatus indexOperationStatus = searchService.bulkIndexName(nameEntries); - updateIsIndexFlag(nameEntries, true, indexOperationStatus); + response.put("message", indexOperationStatus.getMessage()); + + if (!indexOperationStatus.getStatus()) { + return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); + } for (NameEntry nameEntry : nameEntries) { publishNameIsIndexed(nameEntry); @@ -272,8 +273,7 @@ public ResponseEntity> batchIndexEntriesByName(@RequestBody nameEntryService.saveName(nameEntry); } - // TODO re-implement to return the message - return new ResponseEntity<>(HttpStatus.CREATED); + return new ResponseEntity<>(response, HttpStatus.CREATED); } @@ -295,7 +295,6 @@ public ResponseEntity> deleteFromIndex(@PathVariable String if (deleted) { NameEntry nameEntry = nameEntryService.loadName(name); if (nameEntry != null) { - nameEntry.setIndexed(false); nameEntry.setState(State.NEW); nameEntryService.saveName(nameEntry); } @@ -336,21 +335,9 @@ public ResponseEntity> batchDeleteFromIndex(@RequestBody Str } IndexOperationStatus indexOperationStatus = searchService.bulkRemoveFromIndex(nameEntries); - updateIsIndexFlag(nameEntries, false, indexOperationStatus); return returnStatusMessage(notFound, indexOperationStatus); } - private void updateIsIndexFlag(List nameEntries, boolean flag, - IndexOperationStatus indexOperationStatus) { - if (indexOperationStatus.getStatus()) { - List updatedNames = nameEntries.stream().map(entry -> { - entry.setIndexed(flag); - return entry; - }).collect(Collectors.toList()); - - nameEntryService.saveNames(updatedNames); - } - } private ResponseEntity> returnStatusMessage(List notFound, IndexOperationStatus indexOperationStatus) { diff --git a/webapi/src/test/java/org/oruko/dictionary/web/rest/NameApiTest.java b/webapi/src/test/java/org/oruko/dictionary/web/rest/NameApiTest.java index 4a60a963..5014bafe 100644 --- a/webapi/src/test/java/org/oruko/dictionary/web/rest/NameApiTest.java +++ b/webapi/src/test/java/org/oruko/dictionary/web/rest/NameApiTest.java @@ -93,18 +93,6 @@ public void test_get_all_names_via_get() throws Exception { .andExpect(status().isOk()); } - @Test - public void test_get_all_names_filtered_by_is_indexed() throws Exception { - testNameEntry.setIndexed(true); - anotherTestNameEntry.setIndexed(false); - when(entryService.loadAllNames()).thenReturn(Arrays.asList(testNameEntry, anotherTestNameEntry)); - - mockMvc.perform(get("/v1/names?all=true&indexed=true")) - .andExpect(jsonPath("$", hasSize(1))) - .andExpect(jsonPath("$[0].name", is("test-entry"))) - .andExpect(status().isOk()); - } - @Test public void test_get_all_names_filtered_by_is_submitted_by() throws Exception { testNameEntry.setSubmittedBy("test"); diff --git a/website/pom.xml b/website/pom.xml index 7866cb4b..ca492441 100644 --- a/website/pom.xml +++ b/website/pom.xml @@ -26,7 +26,7 @@ ${project.groupId} - jpa-search + jpa-search-module ${project.version} From 81fd76e166675ec9de2e116a414ac4a28f1ba763 Mon Sep 17 00:00:00 2001 From: Dadepo Aderemi Date: Sat, 2 Dec 2017 19:03:02 +0100 Subject: [PATCH 10/12] Uncommenting the ElasticSearchServiceTest --- .../ElasticSearchServiceTest.java | 142 ++++++++++-------- 1 file changed, 76 insertions(+), 66 deletions(-) diff --git a/elasticsearch/src/test/java/org/oruko/dictionary/elasticsearch/ElasticSearchServiceTest.java b/elasticsearch/src/test/java/org/oruko/dictionary/elasticsearch/ElasticSearchServiceTest.java index 620a6cb9..800bc834 100644 --- a/elasticsearch/src/test/java/org/oruko/dictionary/elasticsearch/ElasticSearchServiceTest.java +++ b/elasticsearch/src/test/java/org/oruko/dictionary/elasticsearch/ElasticSearchServiceTest.java @@ -1,7 +1,17 @@ package org.oruko.dictionary.elasticsearch; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.test.ElasticsearchIntegrationTest; +import org.junit.Before; +import org.junit.Test; +import org.oruko.dictionary.model.NameEntry; +import org.oruko.dictionary.search.api.SearchService; +import java.io.IOException; + +import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope; /** @@ -11,71 +21,71 @@ */ @ClusterScope(scope = ElasticsearchIntegrationTest.Scope.TEST) public class ElasticSearchServiceTest extends ElasticsearchIntegrationTest { -// private String dictionary = "dictionary"; -// private ESConfig esConfig; -// SearchService searchService; -// -// private class TestNode implements org.elasticsearch.node.Node { -// -// @Override -// public Settings settings() { -// return null; -// } -// -// @Override -// public Client client() { -// return org.elasticsearch.test.ElasticsearchIntegrationTest.client(); -// } -// -// @Override -// public org.elasticsearch.node.Node start() { -// return null; -// } -// -// @Override -// public org.elasticsearch.node.Node stop() { -// return null; -// } -// -// @Override -// public void close() { -// -// } -// -// @Override -// public boolean isClosed() { -// return false; -// } -// } -// -// @Before -// public void setup() throws IOException { -// -// esConfig = new ESConfig(); -// esConfig.setDocumentType("nameentry"); -// esConfig.setIndexName("dictionary"); -// esConfig.setClusterName("yoruba_name_dictionary"); -// esConfig.setHostName("localhost"); -// esConfig.setPort(9300); -// -// createIndex(dictionary); -// -// flushAndRefresh(); -// -// searchService = new ElasticSearchService(new TestNode(), esConfig); -// } -// -// @Test -// public void testGetByName() throws IOException { -// XContentBuilder lagbaja = jsonBuilder().startObject() -// .field("name", "jamo") -// .endObject(); -// index(esConfig.getIndexName(), esConfig.getDocumentType(), lagbaja); -// -// flushAndRefresh(); -// -// Map result = searchService.getByName("jamo"); -// assertEquals("jamo", result.get("name")); -// } + private String dictionary = "dictionary"; + private ESConfig esConfig; + SearchService searchService; + + private class TestNode implements org.elasticsearch.node.Node { + + @Override + public Settings settings() { + return null; + } + + @Override + public Client client() { + return org.elasticsearch.test.ElasticsearchIntegrationTest.client(); + } + + @Override + public org.elasticsearch.node.Node start() { + return null; + } + + @Override + public org.elasticsearch.node.Node stop() { + return null; + } + + @Override + public void close() { + + } + + @Override + public boolean isClosed() { + return false; + } + } + + @Before + public void setup() throws IOException { + + esConfig = new ESConfig(); + esConfig.setDocumentType("nameentry"); + esConfig.setIndexName("dictionary"); + esConfig.setClusterName("yoruba_name_dictionary"); + esConfig.setHostName("localhost"); + esConfig.setPort(9300); + + createIndex(dictionary); + + flushAndRefresh(); + + searchService = new ElasticSearchService(new TestNode(), esConfig); + } + + @Test + public void testGetByName() throws IOException { + XContentBuilder lagbaja = jsonBuilder().startObject() + .field("name", "jamo") + .endObject(); + index(esConfig.getIndexName(), esConfig.getDocumentType(), lagbaja); + + flushAndRefresh(); + + NameEntry jamo = searchService.getByName("jamo"); + assertEquals("jamo", jamo.getName()); + } } From 56ace7c80c9f0e6795ac26476b83912e34a0d6ac Mon Sep 17 00:00:00 2001 From: Dadepo Aderemi Date: Sat, 2 Dec 2017 19:06:47 +0100 Subject: [PATCH 11/12] Uncommenting @Cacheables --- .../main/java/org/oruko/dictionary/website/ApiService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/website/src/main/java/org/oruko/dictionary/website/ApiService.java b/website/src/main/java/org/oruko/dictionary/website/ApiService.java index 1787126a..985fc2ea 100644 --- a/website/src/main/java/org/oruko/dictionary/website/ApiService.java +++ b/website/src/main/java/org/oruko/dictionary/website/ApiService.java @@ -2,6 +2,7 @@ import org.oruko.dictionary.model.NameEntry; import org.springframework.beans.factory.annotation.Value; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; @@ -31,12 +32,12 @@ public List> getAllNames() { return Arrays.asList(restTemplate.getForObject(APIPATH + "/names", Map[].class)); } - //@Cacheable("querySearchResult") + @Cacheable("querySearchResult") public NameEntry getName(String nameQuery) { return restTemplate.getForObject(APIPATH + "/search/" + nameQuery, NameEntry.class); } - //@Cacheable("names") + @Cacheable("names") public List> searchName(String nameQuery) { return Arrays.asList(restTemplate.getForObject(APIPATH + "/search/?q=" + nameQuery, Map[].class)); } From 8bfaf88a029d2d637845cb58420d69c2505186ee Mon Sep 17 00:00:00 2001 From: Dadepo Aderemi Date: Sat, 2 Dec 2017 19:07:53 +0100 Subject: [PATCH 12/12] Turning back template caching --- website/src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/main/resources/application.properties b/website/src/main/resources/application.properties index 1da26c2d..77f8ad17 100644 --- a/website/src/main/resources/application.properties +++ b/website/src/main/resources/application.properties @@ -26,7 +26,7 @@ endpoints.shutdown.enabled=true # Template (Handlebars) handlebars.prefix: classpath:website/ handlebars.suffix: .hbs -handlebars.cache: false +handlebars.cache: true handlebars.registerMessageHelper: true handlebars.failOnMissingFile: false handlebars.prettyPrint: false