Skip to content

Commit

Permalink
Merge pull request #63 from aodn/context-suggester
Browse files Browse the repository at this point in the history
`search_as_you_type`
  • Loading branch information
utas-raymondng authored Apr 22, 2024
2 parents f6d5070 + f25549e commit a62e8f8
Show file tree
Hide file tree
Showing 43 changed files with 3,602 additions and 480 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ target/
!.mvn
!**/src/main/**/target/
!**/src/test/**/target/
src/main/generated/
**/main/generated/


### STS ###
.apt_generated
Expand Down
75 changes: 75 additions & 0 deletions ardcvocabs/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>es-indexer</artifactId>
<groupId>au.org.aodn</groupId>
<version>0.0.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>ardcvocabs</artifactId>
<packaging>jar</packaging>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>${maven.sourceplugin.version}</version>
<executions>
<execution>
<id>attach-sources</id>
<phase>verify</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package au.org.aodn.ardcvocabs.configuration;

import au.org.aodn.ardcvocabs.service.ArdcVocabsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;

@Slf4j
@AutoConfiguration // More expressive vs @Configuration
@ConditionalOnMissingBean(ArdcVocabsService.class)
public class ArdcAutoConfiguration {

@Bean
public ArdcVocabsService createArdcVocabsService() {
log.info("Create ArdcVocabsService");
return new ArdcVocabsService();
}
/**
* In case the one who use this lib have not created it.
* @return RestTemplate
*/
@Bean
@ConditionalOnMissingBean(RestTemplate.class)
public RestTemplate ardcVocabRestTemplate() {
return new RestTemplate();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package au.org.aodn.ardcvocabs.model;

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

import java.util.List;

/**
* This is the model class for http://vocabs.ardc.edu.au/repository/api/lda/aodn/aodn-parameter-category-vocabulary/
*/
@Builder
@Getter
@Setter
public class CategoryVocabModel {

protected String label;
protected String definition;
protected String about;
protected List<CategoryVocabModel> broader;
protected List<CategoryVocabModel> narrower;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package au.org.aodn.ardcvocabs.service;

import au.org.aodn.ardcvocabs.model.CategoryVocabModel;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;


@Slf4j
public class ArdcVocabsService {
@Autowired
protected RestTemplate ardcVocabRestTemplate;

protected static String path = "/aodn-parameter-category-vocabulary/version-2-1/concept.json";
protected static String leafPath = "/aodn-discovery-parameter-vocabulary/version-1-6/concept.json";

protected static String details = "/aodn-discovery-parameter-vocabulary/version-1-6/resource.json?uri=%s";

protected Function<JsonNode, String> label = (node) -> node.get("prefLabel").get("_value").asText();
protected Function<JsonNode, String> about = (node) -> node.has("_about") ? node.get("_about").asText() : null;
protected Function<JsonNode, String> definition = (node) -> node.has("definition") ? node.get("definition").asText() : null;

protected BiFunction<JsonNode, String, Boolean> isNodeValid = (node, item) -> node != null && !node.isEmpty() && node.has(item) && !node.get(item).isEmpty();

/**
* We want to get the list of leaf node for the API, from there we need to query individual resources to get the broadMatch value
* this value is the link to the second level of the category
*
* API to the details to get the broadMatch
* http://vocabs.ardc.edu.au/repository/api/lda/aodn/aodn-discovery-parameter-vocabulary/version-1-6/resource.json?uri=http://vocab.aodn.org.au/def/discovery_parameter/891
*
* @param vocabApiBase
* @return
*/
protected Map<String, List<CategoryVocabModel>> getLeafNodeOfParameterCategory(String vocabApiBase) {
Map<String, List<CategoryVocabModel>> result = new HashMap<>();
String url = String.format(vocabApiBase + leafPath);

while (url != null) {
log.debug("Query api -> {}", url);

ObjectNode r = ardcVocabRestTemplate.getForObject(url, ObjectNode.class);
if (r != null && !r.isEmpty()) {
JsonNode node = r.get("result");

if (isNodeValid.apply(node, "items")) {
for (JsonNode j : node.get("items")) {
// Now we need to construct link to detail resources
String dl = String.format(vocabApiBase + details, about.apply(j));
try {
log.debug("Query api -> {}", dl);
ObjectNode d = ardcVocabRestTemplate.getForObject(dl, ObjectNode.class);

if(isNodeValid.apply(d, "result") && isNodeValid.apply(d.get("result"), "primaryTopic")) {
JsonNode target = d.get("result").get("primaryTopic");

CategoryVocabModel model = CategoryVocabModel
.builder()
.label(label.apply(target))
.definition(definition.apply(target))
.about(about.apply(target))
.build();

if(target.has("broadMatch") && !target.get("broadMatch").isEmpty()) {
for(JsonNode bm : target.get("broadMatch")) {
if (!result.containsKey(bm.asText())) {
result.put(bm.asText(), new ArrayList<>());
}
// We will have multiple cat under the same parent
result.get(bm.asText()).add(model);
}
}
}
}
catch(Exception e) {
log.error("Item not found in resource {}", dl);
}
}
}

if (!node.isEmpty() && node.has("next")) {
url = node.get("next").asText();
}
else {
url = null;
}
}
else {
url = null;
}
}
return result;
}

public List<CategoryVocabModel> getParameterCategory(String vocabApiBase) {
Map<String, List<CategoryVocabModel>> leaves = getLeafNodeOfParameterCategory(vocabApiBase);
List<CategoryVocabModel> result = new ArrayList<>();

String url = String.format(vocabApiBase + path);

while (url != null) {
try {
log.debug("Query api -> {}", url);

ObjectNode r = ardcVocabRestTemplate.getForObject(url, ObjectNode.class);

if (r != null && !r.isEmpty()) {
JsonNode node = r.get("result");

if (!node.isEmpty() && node.has("items") && !node.get("items").isEmpty()) {
for (JsonNode j : node.get("items")) {
List<CategoryVocabModel> broader = new ArrayList<>();
List<CategoryVocabModel> narrower = new ArrayList<>();

log.debug("Processing label {}", label.apply(j));
if (j.has("broader")) {
for (JsonNode b : j.get("broader")) {
if (b.has("prefLabel") && b.has("_about")) {
CategoryVocabModel c = CategoryVocabModel
.builder()
.about(about.apply(b))
.label(label.apply(b))
.build();
broader.add(c);
}
}
}

if (j.has("narrower")) {
for (JsonNode b : j.get("narrower")) {
if (b.has("prefLabel") && b.has("_about")) {
CategoryVocabModel c = CategoryVocabModel
.builder()
.about(about.apply(b))
.label(label.apply(b))
.build();

narrower.add(c);

// The record comes from ardc have two levels only, so the second level for sure
// is empty, but the third level info comes form another link (aka the leaves)
// and therefore we can attach it to the second level to for the third.
if(leaves.containsKey(about.apply(b))) {
c.setNarrower(leaves.get(about.apply(b)));
}
}
}
}

CategoryVocabModel model = CategoryVocabModel
.builder()
.label(label.apply(j))
.definition(definition.apply(j))
.about(about.apply(j))
.broader(broader)
.narrower(narrower)
.build();

result.add(model);
}
}

if (!node.isEmpty() && node.has("next")) {
url = node.get("next").asText();
}
else {
url = null;
}
}
else {
url = null;
}
} catch (RestClientException e) {
log.error("Fail connect {}, category return likely outdated", url);
url = null;
}
}
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
au.org.aodn.ardcvocabs.configuration.ArdcAutoConfiguration
Loading

0 comments on commit a62e8f8

Please sign in to comment.