From af62b573c1c5d1b796821524c7019dbed296b742 Mon Sep 17 00:00:00 2001
From: "michael.breu" <michael.breu@uibk.ac.at>
Date: Fri, 26 Feb 2021 15:46:15 +0100
Subject: [PATCH] =?UTF-8?q?Hinzuf=C3=BCgen=20von=20HitCounts?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../repository/search/MetaDataRepository.java | 226 +++++++++++-------
 .../uibk/gitsearch/service/SearchService.java |  10 +-
 .../service/dto/AutoCompleteEntry.java        |  41 ++++
 .../gitsearch/web/rest/SearchResource.java    |  10 +-
 .../app/search/service/search-service.ts      |  26 +-
 .../teaserContent.component.html              |   6 +-
 .../teaserContent/teaserContent.component.ts  |  14 +-
 .../search/MetaDataRepositoryTests.java       |  22 +-
 8 files changed, 227 insertions(+), 128 deletions(-)
 create mode 100644 src/main/java/at/ac/uibk/gitsearch/service/dto/AutoCompleteEntry.java

diff --git a/src/main/java/at/ac/uibk/gitsearch/repository/search/MetaDataRepository.java b/src/main/java/at/ac/uibk/gitsearch/repository/search/MetaDataRepository.java
index 2c8b90e56..2bdea4961 100644
--- a/src/main/java/at/ac/uibk/gitsearch/repository/search/MetaDataRepository.java
+++ b/src/main/java/at/ac/uibk/gitsearch/repository/search/MetaDataRepository.java
@@ -4,14 +4,14 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
+import java.util.Map.Entry;
 import java.util.StringTokenizer;
 import java.util.TreeMap;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import org.elasticsearch.action.search.SearchRequest;
 import org.elasticsearch.action.search.SearchResponse;
@@ -32,6 +32,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 
 import at.ac.uibk.gitsearch.config.ApplicationProperties;
+import at.ac.uibk.gitsearch.service.dto.AutoCompleteEntry;
 import at.ac.uibk.gitsearch.service.dto.SearchInputDTO;
 import at.ac.uibk.gitsearch.service.dto.SearchResultsDTO;
 import at.ac.uibk.gitsearch.service.dto.SearchResultsDTO.GitProject;
@@ -40,7 +41,7 @@ import at.ac.uibk.gitsearch.service.dto.SearchResultsDTO.SearchResultDTO;
 
 @Repository
 public class MetaDataRepository {
-
+	
 	/**
 	 * just a local wrapper, to decrypt JSON
 	 *
@@ -66,8 +67,6 @@ public class MetaDataRepository {
 			this.project = project;
 		}
 
-
-
 	}
 
 	private final RestHighLevelClient elasticsearchClient;
@@ -83,10 +82,11 @@ public class MetaDataRepository {
 	}
 
 	/**
-	 * contains for each Field, a list of Strings mapped to a completion.
-	 * E.g. "metadata.creator" -> Kerstin -> {"Kerstin Limbeck", "Kerstin Mair"}
+	 * contains for each Field, a list of Strings mapped to a completion. Together
+	 * with hit counts E.g. "metadata.creator" -> Kerstin -> {"Kerstin Limbeck" ->
+	 * 3, "Kerstin Mair" -> 2)}
 	 */
-	private Map<String, Map<String, Set<String>>> cachedCompletions = null;
+	private Map<String, Map<String, Map<String, Integer>>> cachedCompletions = null;
 
 	private static final int MAX_AUTO_COMPLETION_RESULTS = 10;
 
@@ -97,7 +97,7 @@ public class MetaDataRepository {
 	 * @return
 	 * @throws IOException
 	 */
-	public List<String> getKeywordsAutoComplete(String keyWordPrefix) throws IOException {
+	public List<AutoCompleteEntry> getKeywordsAutoComplete(String keyWordPrefix) throws IOException {
 		return getFieldAutoCompletion(keyWordPrefix, SearchRepositoryConstants.METADATA_KEYWORDS);
 	}
 
@@ -108,8 +108,9 @@ public class MetaDataRepository {
 	 * @return
 	 * @throws IOException
 	 */
-	public List<String> getProgrammingLanguageAutoComplete(String progammingLanguagePrefix) throws IOException {
-		return getFieldAutoCompletion(progammingLanguagePrefix, SearchRepositoryConstants.METADATA_PROGRAMMING_LANGUAGES);
+	public List<AutoCompleteEntry> getProgrammingLanguageAutoComplete(String progammingLanguagePrefix) throws IOException {
+		return getFieldAutoCompletion(progammingLanguagePrefix,
+				SearchRepositoryConstants.METADATA_PROGRAMMING_LANGUAGES);
 	}
 
 	/**
@@ -119,7 +120,7 @@ public class MetaDataRepository {
 	 * @return
 	 * @throws IOException
 	 */
-	public List<String> getCreatorAutoComplete(String creatorPrefix) throws IOException {
+	public List<AutoCompleteEntry> getCreatorAutoComplete(String creatorPrefix) throws IOException {
 		return getFieldAutoCompletion(creatorPrefix, SearchRepositoryConstants.METADATA_CREATOR);
 	}
 
@@ -130,67 +131,115 @@ public class MetaDataRepository {
 	 * @return
 	 * @throws IOException
 	 */
-	public List<String> getContributorAutoComplete(String contributorPrefix) throws IOException {
+	public List<AutoCompleteEntry> getContributorAutoComplete(String contributorPrefix) throws IOException {
 		return getFieldAutoCompletion(contributorPrefix, SearchRepositoryConstants.METADATA_CONTRIBUTOR);
 	}
 
-	private List<String> getFieldAutoCompletion(String keyWordPrefix, final String metaDataField)
+	/**
+	 * returns a list of hits together with the number of occurences
+	 * @param keyWordPrefix
+	 * @param metaDataField
+	 * @return
+	 * @throws IOException
+	 * @throws JsonProcessingException
+	 * @throws JsonMappingException
+	 */
+	private List<AutoCompleteEntry> getFieldAutoCompletion(String keyWordPrefix, final String metaDataField)
 			throws IOException, JsonProcessingException, JsonMappingException {
 		fillAutoCompletion();
-		return cachedCompletions.get(metaDataField).entrySet().stream()
-				.filter(s -> s.getKey().startsWith(keyWordPrefix))
-				.map(Map.Entry::getValue)
-				.flatMap(java.util.Collection::stream).distinct()
-				.limit(MAX_AUTO_COMPLETION_RESULTS).collect(Collectors.toList());
+
+		// höllisch kompliziert und höllisch unverständlich mit Streams :-)
+		// idea
+		// 1. Filtern nach Prefix
+		// 2. Zusammenzählen der möglichen Hits (Besser Max statt Sum?)
+		// 3. Auf MAX_AUTO_COMPLETION_RESULTS begrenzen und nur die Trefferstrings
+		// ausgeben.
+		final Stream<Entry<String, Map<String, Integer>>> filteredEntries = cachedCompletions.get(metaDataField)
+				.entrySet().stream() // Map<String, Map<String, Integer>> -> Stream<Entry<String, Map<String,
+										// Integer>>
+				.filter(s -> s.getKey().startsWith(keyWordPrefix));
+		final Map<String, Integer> combinedHits = filteredEntries // s = String -> bool -> Stream<Entry<String,
+																	// Map<String, Integer>>
+				.flatMap(e -> e.getValue().entrySet().stream()) // Stream< Map<String, Integer>> with duplicates
+				.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, Integer::sum));
+		return combinedHits.entrySet().stream().sorted((e1, e2) -> e2.getValue() - e1.getValue())
+				.limit(MAX_AUTO_COMPLETION_RESULTS).map(s -> new AutoCompleteEntry(s.getKey(), s.getValue())).collect(Collectors.toList()); // Map.Entry<String,
+																										// Map<String,
+																										// Integer>>
+
 	}
 
 	private synchronized void fillAutoCompletion() throws IOException, JsonProcessingException, JsonMappingException {
 		if (cachedCompletions == null) {
-			Map<String, Map<String, Set<String>>> reloadedCachedCompletions = new HashMap<>();
+			Map<String, Map<String, Map<String, Integer>>> reloadedCachedCompletions = new HashMap<>();
 			reloadedCachedCompletions.put(SearchRepositoryConstants.METADATA_KEYWORDS, new TreeMap<>());
 			reloadedCachedCompletions.put(SearchRepositoryConstants.METADATA_CREATOR, new TreeMap<>());
 			reloadedCachedCompletions.put(SearchRepositoryConstants.METADATA_CONTRIBUTOR, new TreeMap<>());
 			reloadedCachedCompletions.put(SearchRepositoryConstants.METADATA_PROGRAMMING_LANGUAGES, new TreeMap<>());
 
 			SearchRequest searchRequest = new SearchRequest(SearchRepositoryConstants.INDEX_METADATA);
-			SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().size(1000).from(0);
-			
-			searchRequest.source(sourceBuilder);
-
-			SearchResponse searchResponse = elasticsearchClient.search(searchRequest, RequestOptions.DEFAULT);
-			ObjectMapper objectMapper = new ObjectMapper();
-			objectMapper.registerModule(new JavaTimeModule());
-			objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
-
-			for (SearchHit searchHit : searchResponse.getHits().getHits()) {
-				final String sourceAsJSON = searchHit.getSourceAsString();
-				SearchResultDTOWrapper entry = objectMapper.readValue(sourceAsJSON, SearchResultDTOWrapper.class);
-				if (entry.getMetadata().getKeyword() != null)
-					for (String keyWord : entry.getMetadata().getKeyword())
-						addTo(reloadedCachedCompletions.get(SearchRepositoryConstants.METADATA_KEYWORDS), split(keyWord), keyWord);
-				if (entry.getMetadata().getContributor() != null)
-					for (Person contributor : entry.getMetadata().getContributor())
-						addTo(reloadedCachedCompletions.get(SearchRepositoryConstants.METADATA_CONTRIBUTOR), split(contributor.getName()), contributor.getName());
-				if (entry.getMetadata().getCreator() != null)
-					for (Person creator : entry.getMetadata().getCreator())
-						addTo(reloadedCachedCompletions.get(SearchRepositoryConstants.METADATA_CREATOR),
-								split(creator.getName()), creator.getName());
-				if (entry.getMetadata().getProgrammingLanguage() != null)
-					for (String language : entry.getMetadata().getProgrammingLanguage())
-						addTo(reloadedCachedCompletions.get(SearchRepositoryConstants.METADATA_PROGRAMMING_LANGUAGES), Collections.singletonList(language), language);
+
+			final int pageSize = 20;
+			int currentPage = 0;
+			boolean tryNextPage = true;
+
+			while (tryNextPage) {
+
+				SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().size(pageSize).from(currentPage++*pageSize);
+				
+				searchRequest.source(sourceBuilder);
+
+				SearchResponse searchResponse = elasticsearchClient.search(searchRequest, RequestOptions.DEFAULT);
+
+				ObjectMapper objectMapper = new ObjectMapper();
+				objectMapper.registerModule(new JavaTimeModule());
+				objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+
+				tryNextPage = searchResponse.getHits().getHits().length > 0;
+				
+				for (SearchHit searchHit : searchResponse.getHits().getHits()) {
+					final String sourceAsJSON = searchHit.getSourceAsString();
+					SearchResultDTOWrapper entry = objectMapper.readValue(sourceAsJSON, SearchResultDTOWrapper.class);
+					if (entry.getMetadata().getKeyword() != null)
+						for (String keyWord : entry.getMetadata().getKeyword())
+							addTo(reloadedCachedCompletions.get(SearchRepositoryConstants.METADATA_KEYWORDS),
+									split(keyWord), keyWord);
+					if (entry.getMetadata().getContributor() != null)
+						for (Person contributor : entry.getMetadata().getContributor())
+							addTo(reloadedCachedCompletions.get(SearchRepositoryConstants.METADATA_CONTRIBUTOR),
+									split(contributor.getName()), contributor.getName());
+					if (entry.getMetadata().getCreator() != null)
+						for (Person creator : entry.getMetadata().getCreator())
+							addTo(reloadedCachedCompletions.get(SearchRepositoryConstants.METADATA_CREATOR),
+									split(creator.getName()), creator.getName());
+					if (entry.getMetadata().getProgrammingLanguage() != null)
+						for (String language : entry.getMetadata().getProgrammingLanguage())
+							addTo(reloadedCachedCompletions
+									.get(SearchRepositoryConstants.METADATA_PROGRAMMING_LANGUAGES),
+									Collections.singletonList(language), language);
+				}
 			}
 			cachedCompletions = reloadedCachedCompletions;
 		}
 	}
 
-	private void addTo(Map<String, Set<String>> completionMap, List<String> tokenList, String target) {
+	private void addTo(Map<String, Map<String, Integer>> completionMap, List<String> tokenList, String target) {
 		tokenList.forEach(token -> {
-			Set<String> existingTargets = completionMap.get(token);
-			if(existingTargets == null) {
-				existingTargets = new HashSet<>();
-				completionMap.put(token,  existingTargets);
+			Map<String, Integer> existingTargets = completionMap.get(token);
+			if (existingTargets == null) { // none found
+				existingTargets = new HashMap<>();
+				existingTargets.put(target, 1);
+				completionMap.put(token, existingTargets);
+			} else {
+				final Integer count = existingTargets.get(target);
+				if (count == null) {
+					existingTargets.put(target, 1);
+				} else {
+					existingTargets.put(target, count + 1);
+				}
+
 			}
-			existingTargets.add(target);
+//			existingTargets.add(target);
 		});
 	}
 
@@ -209,55 +258,44 @@ public class MetaDataRepository {
 
 		BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
 		forEachTerm(searchInputDTO.getFulltextQuery(),
-					term ->
-					  queryBuilder.must(QueryBuilders.multiMatchQuery(
-						term,
-						SearchRepositoryConstants.METADATA_DESCRIPTION,
-						SearchRepositoryConstants.METADATA_KEYWORDS,
-						SearchRepositoryConstants.METADATA_TITLE)
-						.type(MultiMatchQueryBuilder.Type.PHRASE_PREFIX)
-						));
-
-		forEachTerm(searchInputDTO.getMetadata().getProgrammingLanguage(),
-					term ->
-						queryBuilder.must(QueryBuilders.prefixQuery(SearchRepositoryConstants.METADATA_PROGRAMMING_LANGUAGES, term)));
-
-		forEachTerm(searchInputDTO.getMetadata().getKeyword(),
-				term -> 
-					queryBuilder.must(QueryBuilders.prefixQuery(SearchRepositoryConstants.METADATA_KEYWORDS, term)));
-
-		if(searchInputDTO.getMetadata().getAuthor()!=null)	
-				 queryBuilder.must(QueryBuilders.multiMatchQuery(searchInputDTO.getMetadata().getAuthor(), 
-						      SearchRepositoryConstants.METADATA_CREATOR, 
-						      SearchRepositoryConstants.METADATA_CONTRIBUTOR,
-						      SearchRepositoryConstants.METADATA_PUBLISHER).type(MultiMatchQueryBuilder.Type.PHRASE_PREFIX));
+				term -> queryBuilder.must(QueryBuilders
+						.multiMatchQuery(term, SearchRepositoryConstants.METADATA_DESCRIPTION,
+								SearchRepositoryConstants.METADATA_KEYWORDS, SearchRepositoryConstants.METADATA_TITLE)
+						.type(MultiMatchQueryBuilder.Type.PHRASE_PREFIX)));
+
+		forEachTerm(searchInputDTO.getMetadata().getProgrammingLanguage(), term -> queryBuilder
+				.must(QueryBuilders.prefixQuery(SearchRepositoryConstants.METADATA_PROGRAMMING_LANGUAGES, term)));
+
+		forEachTerm(searchInputDTO.getMetadata().getKeyword(), term -> queryBuilder
+				.must(QueryBuilders.prefixQuery(SearchRepositoryConstants.METADATA_KEYWORDS, term)));
+
+		if (searchInputDTO.getMetadata().getAuthor() != null)
+			queryBuilder.must(QueryBuilders
+					.multiMatchQuery(searchInputDTO.getMetadata().getAuthor(),
+							SearchRepositoryConstants.METADATA_CREATOR, SearchRepositoryConstants.METADATA_CONTRIBUTOR,
+							SearchRepositoryConstants.METADATA_PUBLISHER)
+					.type(MultiMatchQueryBuilder.Type.PHRASE_PREFIX));
 
 //		forEachTerm(searchInputDTO.getMetadata().getAuthor(),		
 //		   term ->
 // 			queryBuilder.must(QueryBuilders.prefixQuery(SearchRepositoryConstants.METADATA_CREATOR, term)));
 
-		forEachTerm(searchInputDTO.getMetadata().getLicense(),		
-				   term ->
-			queryBuilder.must(QueryBuilders.prefixQuery(SearchRepositoryConstants.METADATA_LICENSE, term)));
+		forEachTerm(searchInputDTO.getMetadata().getLicense(),
+				term -> queryBuilder.must(QueryBuilders.prefixQuery(SearchRepositoryConstants.METADATA_LICENSE, term)));
 
-        char[] boundaryChars = {'\n'};
+		char[] boundaryChars = { '\n' };
 
-        HighlightBuilder highlightBuilder = new HighlightBuilder()
-            .highlighterType("plain")
-            .preTags(properties.getSearch().getHighlightPre())
-            .postTags(properties.getSearch().getHighlightPost())
-            .field(SearchRepositoryConstants.METADATA_DESCRIPTION)
-            .boundaryChars(boundaryChars)
-            .boundaryScannerType(HighlightBuilder.BoundaryScannerType.SENTENCE)
-            .fragmentSize(FRAGMENT_SIZE);
+		HighlightBuilder highlightBuilder = new HighlightBuilder().highlighterType("plain")
+				.preTags(properties.getSearch().getHighlightPre()).postTags(properties.getSearch().getHighlightPost())
+				.field(SearchRepositoryConstants.METADATA_DESCRIPTION).boundaryChars(boundaryChars)
+				.boundaryScannerType(HighlightBuilder.BoundaryScannerType.SENTENCE).fragmentSize(FRAGMENT_SIZE);
 
-        // String[] includes = {};
+		// String[] includes = {};
 		// String[] excludes = {HighlightRepositoryConstants.CONTENT_FIELD};
 
 		SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
-		sourceBuilder.query(queryBuilder)
-            .highlighter(highlightBuilder)
-				.size(pageSize).from((searchInputDTO.getPage()) * pageSize);
+		sourceBuilder.query(queryBuilder).highlighter(highlightBuilder).size(pageSize)
+				.from((searchInputDTO.getPage()) * pageSize);
 		// .fetchSource(includes, excludes);
 
 		searchRequest.source(sourceBuilder);
@@ -271,7 +309,8 @@ public class MetaDataRepository {
 		final SearchResultsDTO results = new SearchResultsDTO();
 		List<SearchResultDTO> searchResults = new ArrayList<>();
 		for (SearchHit searchHit : searchResponse.getHits().getHits()) {
-			SearchResultDTOWrapper entry = objectMapper.readValue(searchHit.getSourceAsString(), SearchResultDTOWrapper.class);
+			SearchResultDTOWrapper entry = objectMapper.readValue(searchHit.getSourceAsString(),
+					SearchResultDTOWrapper.class);
 			final SearchResultDTO metadata = entry.getMetadata();
 			metadata.setProject(entry.getProject());
 			searchResults.add(metadata);
@@ -281,12 +320,13 @@ public class MetaDataRepository {
 		results.setSearchResult(searchResults);
 		return results;
 	}
-	
+
 	/** Helper **/
 	private void forEachTerm(String searchTerm, Consumer<String> exec) {
-		if(searchTerm == null) return;
+		if (searchTerm == null)
+			return;
 		StringTokenizer st = new StringTokenizer(searchTerm);
-		while(st.hasMoreTokens()) {
+		while (st.hasMoreTokens()) {
 			exec.accept(st.nextToken());
 		}
 	}
diff --git a/src/main/java/at/ac/uibk/gitsearch/service/SearchService.java b/src/main/java/at/ac/uibk/gitsearch/service/SearchService.java
index 09f5af13f..2e1bb5a94 100644
--- a/src/main/java/at/ac/uibk/gitsearch/service/SearchService.java
+++ b/src/main/java/at/ac/uibk/gitsearch/service/SearchService.java
@@ -7,6 +7,7 @@ import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map.Entry;
 import java.util.Optional;
 import java.util.stream.Collectors;
 
@@ -28,6 +29,7 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
 
 import at.ac.uibk.gitsearch.repository.search.MetaDataRepository;
 import at.ac.uibk.gitsearch.security.jwt.TokenProvider;
+import at.ac.uibk.gitsearch.service.dto.AutoCompleteEntry;
 import at.ac.uibk.gitsearch.service.dto.SearchInputDTO;
 import at.ac.uibk.gitsearch.service.dto.SearchResultsDTO;
 import at.ac.uibk.gitsearch.service.dto.SearchResultsDTO.SearchResultDTO;
@@ -60,7 +62,7 @@ public class SearchService {
 	 * @return
 	 * @throws IOException
 	 */
-	public List<String> getKeywordsAutoComplete(String keyWordPrefix) throws IOException {
+	public List<AutoCompleteEntry> getKeywordsAutoComplete(String keyWordPrefix) throws IOException {
 		return metaDataRepository.getKeywordsAutoComplete(keyWordPrefix);
 	}
 
@@ -71,7 +73,7 @@ public class SearchService {
 	 * @return
 	 * @throws IOException
 	 */
-	public List<String> getCreatorAutoComplete(String creatorPrefix) throws IOException {
+	public List<AutoCompleteEntry> getCreatorAutoComplete(String creatorPrefix) throws IOException {
 		return metaDataRepository.getCreatorAutoComplete(creatorPrefix);
 	}
 
@@ -82,7 +84,7 @@ public class SearchService {
 	 * @return
 	 * @throws IOException
 	 */
-	public List<String> getContributorAutoComplete(String contributorPrefix) throws IOException {
+	public List<AutoCompleteEntry> getContributorAutoComplete(String contributorPrefix) throws IOException {
 		return metaDataRepository.getContributorAutoComplete(contributorPrefix);
 	}
 
@@ -93,7 +95,7 @@ public class SearchService {
 	 * @return
 	 * @throws IOException
 	 */
-	public List<String> getProgrammingLanguageAutoComplete(String programmingLanguagePrefix) throws IOException {
+	public List<AutoCompleteEntry> getProgrammingLanguageAutoComplete(String programmingLanguagePrefix) throws IOException {
 		return metaDataRepository.getProgrammingLanguageAutoComplete(programmingLanguagePrefix);
 	}
 
diff --git a/src/main/java/at/ac/uibk/gitsearch/service/dto/AutoCompleteEntry.java b/src/main/java/at/ac/uibk/gitsearch/service/dto/AutoCompleteEntry.java
new file mode 100644
index 000000000..c39397da5
--- /dev/null
+++ b/src/main/java/at/ac/uibk/gitsearch/service/dto/AutoCompleteEntry.java
@@ -0,0 +1,41 @@
+package at.ac.uibk.gitsearch.service.dto;
+
+/**
+ * just contains a pair of a target string and the hit count.
+ * @author Michael Breu
+ *
+ */
+public class AutoCompleteEntry {
+	private String target;
+	private int hitCount;
+	
+	public AutoCompleteEntry(String target, int hitCount) {
+		super();
+		this.target = target;
+		this.hitCount = hitCount;
+	}
+
+	public AutoCompleteEntry(String target) {
+		super();
+		this.target = target;
+		this.hitCount = 1;
+	}
+	
+	public void increaseHitCount() {
+		this.hitCount++;
+	}
+	
+	/**
+	 * @return the target
+	 */
+	public String getTarget() {
+		return target;
+	}
+	/**
+	 * @return the hitCount
+	 */
+	public int getHitCount() {
+		return hitCount;
+	}
+
+}
\ No newline at end of file
diff --git a/src/main/java/at/ac/uibk/gitsearch/web/rest/SearchResource.java b/src/main/java/at/ac/uibk/gitsearch/web/rest/SearchResource.java
index 49c779344..5c77753c0 100644
--- a/src/main/java/at/ac/uibk/gitsearch/web/rest/SearchResource.java
+++ b/src/main/java/at/ac/uibk/gitsearch/web/rest/SearchResource.java
@@ -2,6 +2,7 @@ package at.ac.uibk.gitsearch.web.rest;
 
 import java.io.IOException;
 import java.util.List;
+import java.util.Map.Entry;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -15,6 +16,7 @@ import org.springframework.web.bind.annotation.RestController;
 
 import at.ac.uibk.gitsearch.es.model.DocumentInfo;
 import at.ac.uibk.gitsearch.service.SearchService;
+import at.ac.uibk.gitsearch.service.dto.AutoCompleteEntry;
 import at.ac.uibk.gitsearch.service.dto.SearchInputDTO;
 import at.ac.uibk.gitsearch.service.dto.SearchResultsDTO;
 
@@ -56,7 +58,7 @@ public class SearchResource {
 	 * @throws IOException
 	 */
     @GetMapping("/search/keywordsAutoComplete")
-	public List<String> getKeywordsAutoComplete(@RequestParam String keyWordPrefix) throws IOException {
+	public List<AutoCompleteEntry> getKeywordsAutoComplete(@RequestParam String keyWordPrefix) throws IOException {
 		return searchService.getKeywordsAutoComplete(keyWordPrefix);
 	}
 
@@ -68,7 +70,7 @@ public class SearchResource {
 	 * @throws IOException
 	 */
     @GetMapping("/search/creatorAutoComplete")
-	public List<String> getCreatorAutoComplete(@RequestParam String creatorPrefix) throws IOException {
+	public List<AutoCompleteEntry> getCreatorAutoComplete(@RequestParam String creatorPrefix) throws IOException {
 		return searchService.getCreatorAutoComplete(creatorPrefix);
 	}
 
@@ -80,7 +82,7 @@ public class SearchResource {
 	 * @throws IOException
 	 */
     @GetMapping("/search/contributorAutoComplete")
-	public List<String> getContributorAutoComplete(@RequestParam String contributorPrefix) throws IOException {
+	public List<AutoCompleteEntry> getContributorAutoComplete(@RequestParam String contributorPrefix) throws IOException {
 		return searchService.getContributorAutoComplete(contributorPrefix);
 	}
 
@@ -92,7 +94,7 @@ public class SearchResource {
 	 * @throws IOException
 	 */
     @GetMapping("/search/programmingLanguageAutoComplete")
-	public List<String> getProgrammingLanguageAutoComplete(@RequestParam String programmingLanguagePrefix) throws IOException {
+	public List<AutoCompleteEntry> getProgrammingLanguageAutoComplete(@RequestParam String programmingLanguagePrefix) throws IOException {
 		return searchService.getProgrammingLanguageAutoComplete(programmingLanguagePrefix);
 	}
 
diff --git a/src/main/webapp/app/search/service/search-service.ts b/src/main/webapp/app/search/service/search-service.ts
index 8fb2dc0f3..55fd7220f 100644
--- a/src/main/webapp/app/search/service/search-service.ts
+++ b/src/main/webapp/app/search/service/search-service.ts
@@ -1,12 +1,11 @@
 import { Injectable } from '@angular/core';
-import { HttpClient, HttpResponse, HttpParams } from '@angular/common/http';
+import { HttpClient, HttpParams } from '@angular/common/http';
 import { Observable } from 'rxjs';
 
 import { SERVER_API_URL } from 'app/app.constants';
 import { SearchResultsDTO } from 'app/shared/model/search/search-results-dto.model';
 import { SearchInput } from 'app/shared/model/search/search-input.model';
 
-type EntityResponseTypeAutoComplete = HttpResponse<Array<string>>;
 
 @Injectable({ providedIn: 'root' })
 export class SearchService {
@@ -21,27 +20,38 @@ export class SearchService {
     return this.http.post<SearchResultsDTO>(this.resourceSearchUrlPageDetails, searchInput);
   }
 
-  getKeywordsAutoComplete(prefix: string): Observable<Array<string>> {
+  getKeywordsAutoComplete(prefix: string): Observable<Array<AutoCompletionEntry>> {
     const options: HttpParams = new HttpParams();
     options.append('keyWordPrefix', prefix);
-    return this.http.get<Array<string>>(this.resourceKeywordsAutoCompleteDetails, {
+    return this.http.get<Array<AutoCompletionEntry>>(this.resourceKeywordsAutoCompleteDetails, {
       params: new HttpParams().set('keyWordPrefix', prefix),
     });
   }
 
-  getProgrammingLanguageAutoComplete(prefix: string): Observable<Array<string>> {
+  getProgrammingLanguageAutoComplete(prefix: string): Observable<Array<AutoCompletionEntry>> {
     const options: HttpParams = new HttpParams();
     options.append('keyWordPrefix', prefix);
-    return this.http.get<Array<string>>(this.resourceProgrammingLanguageAutoCompleteDetails, {
+    return this.http.get<Array<AutoCompletionEntry>>(this.resourceProgrammingLanguageAutoCompleteDetails, {
       params: new HttpParams().set('programmingLanguagePrefix', prefix),
     });
   }
 
-  getContributorAutoComplete(prefix: string): Observable<Array<string>> {
+  getContributorAutoComplete(prefix: string): Observable<Array<AutoCompletionEntry>> {
     const options: HttpParams = new HttpParams();
     options.append('keyWordPrefix', prefix);
-    return this.http.get<Array<string>>(this.resourceContributorAutoCompleteDetails, {
+    return this.http.get<Array<AutoCompletionEntry>>(this.resourceContributorAutoCompleteDetails, {
       params: new HttpParams().set('contributorPrefix', prefix),
     });
   }
 }
+
+export class AutoCompletionEntry {
+	public target: String;
+	public hitCount: Number;
+	
+	constructor() {
+		this.target='';
+		this.hitCount=0;
+	} 
+}
+
diff --git a/src/main/webapp/app/teaserContent/teaserContent.component.html b/src/main/webapp/app/teaserContent/teaserContent.component.html
index a62deef5c..bc3c3d65a 100644
--- a/src/main/webapp/app/teaserContent/teaserContent.component.html
+++ b/src/main/webapp/app/teaserContent/teaserContent.component.html
@@ -3,19 +3,19 @@
 	<div class="col-sm-3">
 	  <p style="padding-left: 30px;"><strong>Keywords</strong></p>
 	  <ul style="list-style-type: circle;">
-                <li *ngFor="let keyWord of keywords"><a (click)="clickKeyword(keyWord)"  style="cursor:pointer;">{{keyWord}}</a></li>
+                <li *ngFor="let keyWord of keywords"><a (click)="clickKeyword(keyWord.target)"  style="cursor:pointer;">{{keyWord.target}} ({{keyWord.hitCount}})</a></li>
       </ul>
     </div>
 	<div class="col-sm-3">
 	  <p style="padding-left: 30px;"><strong>Programming Languages</strong></p>
 	  <ul style="list-style-type: circle;">
-                <li *ngFor="let programmingLanguage of programmingLanguages"><a (click)="clickLanguage(programmingLanguage)"  style="cursor:pointer;">{{programmingLanguage}}</a></li>
+                <li *ngFor="let programmingLanguage of programmingLanguages"><a (click)="clickLanguage(programmingLanguage.target)"  style="cursor:pointer;">{{programmingLanguage.target}} ({{programmingLanguage.hitCount}})</a></li>
       </ul>
     </div>
 	<div class="col-sm-3">
 	  <p style="padding-left: 30px;"><strong>Contributors</strong></p>
 	  <ul style="list-style-type: circle;">
-                <li *ngFor="let contributor of contributors"><a (click)="clickContributor(contributor)" style="cursor:pointer;">{{contributor}}</a></li>
+                <li *ngFor="let contributor of contributors"><a (click)="clickContributor(contributor.target)" style="cursor:pointer;">{{contributor.target}} ({{contributor.hitCount}})</a></li>
       </ul>
     </div>
 </div>
\ No newline at end of file
diff --git a/src/main/webapp/app/teaserContent/teaserContent.component.ts b/src/main/webapp/app/teaserContent/teaserContent.component.ts
index 5740293cd..d927d7b1f 100644
--- a/src/main/webapp/app/teaserContent/teaserContent.component.ts
+++ b/src/main/webapp/app/teaserContent/teaserContent.component.ts
@@ -1,6 +1,6 @@
 import { Component, OnInit } from '@angular/core';
 
-import { SearchService } from 'app/search/service/search-service';
+import { SearchService, AutoCompletionEntry } from 'app/search/service/search-service';
 import {SearchInputComponent} from 'app/search-input/search-input.component'
 
 import { Router } from '@angular/router';
@@ -13,9 +13,9 @@ import { Router } from '@angular/router';
   providers: [SearchInputComponent]
 })
 export class TeaserContentComponent implements OnInit {
-  public keywords: Array<String> = new Array<String>();
-  public contributors: Array<String> = new Array<String>();
-  public programmingLanguages: Array<String> = new Array<String>();
+  public keywords: Array<AutoCompletionEntry> = new Array<AutoCompletionEntry>();
+  public contributors: Array<AutoCompletionEntry> = new Array<AutoCompletionEntry>();
+  public programmingLanguages: Array<AutoCompletionEntry> = new Array<AutoCompletionEntry>();
 
   constructor(
 	private searchService: SearchService,
@@ -24,7 +24,7 @@ export class TeaserContentComponent implements OnInit {
 
   ngOnInit(): void {
     this.searchService.getKeywordsAutoComplete('').subscribe(
-      (data: Array<string>) => {
+      (data: Array<AutoCompletionEntry>) => {
         this.keywords = data;
       },
       () => {
@@ -33,7 +33,7 @@ export class TeaserContentComponent implements OnInit {
     );
 
     this.searchService.getProgrammingLanguageAutoComplete('').subscribe(
-      (data: Array<string>) => {
+      (data: Array<AutoCompletionEntry>) => {
         this.programmingLanguages = data;
       },
       () => {
@@ -41,7 +41,7 @@ export class TeaserContentComponent implements OnInit {
       }
     );
     this.searchService.getContributorAutoComplete('').subscribe(
-      (data: Array<string>) => {
+      (data: Array<AutoCompletionEntry>) => {
         this.contributors = data;
       },
       () => {
diff --git a/src/test/java/at/ac/uibk/gitsearch/repository/search/MetaDataRepositoryTests.java b/src/test/java/at/ac/uibk/gitsearch/repository/search/MetaDataRepositoryTests.java
index 5810cc31e..aaa29eedb 100644
--- a/src/test/java/at/ac/uibk/gitsearch/repository/search/MetaDataRepositoryTests.java
+++ b/src/test/java/at/ac/uibk/gitsearch/repository/search/MetaDataRepositoryTests.java
@@ -1,5 +1,9 @@
 package at.ac.uibk.gitsearch.repository.search;
 
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.hasProperty;
+import static org.hamcrest.Matchers.is;
+
 import java.io.IOException;
 import java.util.List;
 
@@ -8,7 +12,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 
 import at.ac.uibk.gitsearch.GitsearchApp;
-import static org.hamcrest.Matchers.*;
+import at.ac.uibk.gitsearch.service.dto.AutoCompleteEntry;
 
 @SpringBootTest(classes = GitsearchApp.class)
 
@@ -18,22 +22,22 @@ public class MetaDataRepositoryTests {
 	
 	@Test
 	public void testKeywordAutocompletion() throws IOException {
-		final List<String> keywordsAutoComplete = metaDataRepository.getKeywordsAutoComplete("Jav");
-		org.junit.Assert.assertThat(keywordsAutoComplete, contains(is("Java")));
+		final List<AutoCompleteEntry> keywordsAutoComplete = metaDataRepository.getKeywordsAutoComplete("Jav");
+		org.junit.Assert.assertThat(keywordsAutoComplete, contains(hasProperty("target",is("Java"))));
 	}
 
 	@Test
 	public void testCreatorAutocompletion() throws IOException {
-		final List<String> creatorAutoComplete = metaDataRepository.getCreatorAutoComplete("Pod");
-		org.junit.Assert.assertThat(creatorAutoComplete, contains(is("Stefan Podlipnig")));
-		final List<String> creatorAutoComplete2 = metaDataRepository.getCreatorAutoComplete("Po");
-		org.junit.Assert.assertThat(creatorAutoComplete2, contains(is("Stefan Podlipnig")));
+		final List<AutoCompleteEntry> creatorAutoComplete = metaDataRepository.getCreatorAutoComplete("Pod");
+		org.junit.Assert.assertThat(creatorAutoComplete, contains(hasProperty("target",is("Stefan Podlipnig"))));
+		final List<AutoCompleteEntry> creatorAutoComplete2 = metaDataRepository.getCreatorAutoComplete("Po");
+		org.junit.Assert.assertThat(creatorAutoComplete2, contains(hasProperty("target",is("Stefan Podlipnig"))));
 	}
 	
 	@Test
 	public void testContributorAutocompletion() throws IOException {
-		final List<String> contributorAutoComplete = metaDataRepository.getContributorAutoComplete("Bast");
-		org.junit.Assert.assertThat(contributorAutoComplete, contains(is("Daniel Bastta")));
+		final List<AutoCompleteEntry> contributorAutoComplete = metaDataRepository.getContributorAutoComplete("Bast");
+		org.junit.Assert.assertThat(contributorAutoComplete, contains(hasProperty("target",is("Daniel Bastta"))));
 	}
 
 }
-- 
GitLab