Skip to content

Commit

Permalink
Add endpoints listing reference data
Browse files Browse the repository at this point in the history
  • Loading branch information
yrodiere committed Mar 27, 2024
1 parent 45a19c4 commit bffa25c
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 3 deletions.
4 changes: 4 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-cache</artifactId>
</dependency>
<dependency>
<groupId>io.quarkiverse.helm</groupId>
<artifactId>quarkus-helm</artifactId>
Expand Down
22 changes: 22 additions & 0 deletions src/main/java/io/quarkus/search/app/QuarkusVersions.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkus.search.app;

import java.util.Comparator;

public final class QuarkusVersions {
private QuarkusVersions() {
}
Expand All @@ -8,4 +10,24 @@ private QuarkusVersions() {
public static final String MAIN = "main";
public static final String V3_2 = "3.2";

public static final Comparator<String> COMPARATOR = new Comparator<String>() {
@Override
public int compare(String left, String right) {
if (left.equals(right)) {
return 0;
} else if (left.equals(MAIN)) {
return 1;
} else if (right.equals(MAIN)) {
return -1;
} else if (left.equals(LATEST)) {
// "latest" actually means "latest non-snapshot", so it's older than main.
return 1;
} else if (right.equals(LATEST)) {
return -1;
} else {
return left.compareTo(right);
}
}
};

}
82 changes: 82 additions & 0 deletions src/main/java/io/quarkus/search/app/ReferenceService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package io.quarkus.search.app;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import io.quarkus.search.app.cache.MethodNameCacheKeyGenerator;
import io.quarkus.search.app.entity.Guide;
import io.quarkus.search.app.entity.Language;

import io.quarkus.cache.Cache;
import io.quarkus.cache.CacheName;
import io.quarkus.cache.CacheResult;

import org.hibernate.search.engine.search.aggregation.AggregationKey;
import org.hibernate.search.mapper.orm.session.SearchSession;

import org.eclipse.microprofile.openapi.annotations.Operation;

@ApplicationScoped
@Path("/")
@Transactional
@org.jboss.resteasy.reactive.Cache(maxAge = 120)
public class ReferenceService {

private static final String REFERENCE_CACHE = "reference-cache";

@CacheName(REFERENCE_CACHE)
Cache cache;

@Inject
SearchSession session;

@GET
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "List available versions")
@Path("/versions")
@CacheResult(cacheName = REFERENCE_CACHE, keyGenerator = MethodNameCacheKeyGenerator.class)
public List<String> versions() {
return listAllValues("quarkusVersion", QuarkusVersions.COMPARATOR.reversed());
}

@GET
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "List available languages")
@Path("/languages")
@CacheResult(cacheName = REFERENCE_CACHE, keyGenerator = MethodNameCacheKeyGenerator.class)
public List<String> languages() {
return Arrays.stream(Language.values()).map(lang -> lang.code).toList();
}

@GET
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "List available categories")
@Path("/categories")
@CacheResult(cacheName = REFERENCE_CACHE, keyGenerator = MethodNameCacheKeyGenerator.class)
public List<String> categories() {
return listAllValues("categories", Comparator.naturalOrder());
}

public void invalidateCaches() {
cache.invalidateAll();
}

private List<String> listAllValues(String fieldName, Comparator<String> comparator) {
var aggKey = AggregationKey.<Map<String, Long>> of("versions");
var result = session.search(Guide.class)
.where(f -> f.matchAll())
.aggregation(aggKey, f -> f.terms().field(fieldName, String.class))
.fetch(0);
return result.aggregation(aggKey).keySet().stream().sorted(comparator).toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.quarkus.search.app.cache;

import java.lang.reflect.Method;

import io.quarkus.cache.CacheKeyGenerator;

public class MethodNameCacheKeyGenerator implements CacheKeyGenerator {
@Override
public Object generate(Method method, Object... methodParams) {
return method.getName();
}
}
6 changes: 3 additions & 3 deletions src/main/java/io/quarkus/search/app/entity/Guide.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import org.hibernate.search.engine.backend.types.Sortable;
import org.hibernate.search.engine.backend.types.TermVector;
import org.hibernate.search.mapper.pojo.automaticindexing.ReindexOnUpdate;
import org.hibernate.search.mapper.pojo.bridge.builtin.annotation.AlternativeDiscriminator;
import org.hibernate.search.mapper.pojo.bridge.mapping.annotation.RoutingBinderRef;
import org.hibernate.search.mapper.pojo.bridge.mapping.annotation.ValueBridgeRef;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed;
Expand All @@ -42,10 +41,11 @@ public class Guide {
@JavaType(URIType.class)
public URI url;

@AlternativeDiscriminator
@Enumerated(EnumType.STRING)
@KeywordField(searchable = Searchable.NO, aggregable = Aggregable.YES)
public Language language;

@KeywordField(searchable = Searchable.NO, aggregable = Aggregable.YES)
public String quarkusVersion;

@KeywordField
Expand Down Expand Up @@ -76,7 +76,7 @@ public class Guide {
@IndexingDependency(reindexOnUpdate = ReindexOnUpdate.NO)
public I18nData<InputProvider> htmlFullContentProvider = new I18nData<>();

@KeywordField(name = "categories")
@KeywordField(name = "categories", aggregable = Aggregable.YES)
public Set<String> categories = Set.of();

@I18nFullTextField(name = "topics", analyzerPrefix = AnalysisConfigurer.DEFAULT, searchAnalyzerPrefix = AnalysisConfigurer.DEFAULT_SEARCH)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;

import io.quarkus.search.app.ReferenceService;
import io.quarkus.search.app.fetching.FetchingService;
import io.quarkus.search.app.quarkusio.QuarkusIO;
import io.quarkus.search.app.util.SimpleExecutor;
Expand Down Expand Up @@ -57,6 +58,9 @@ public class IndexingService {
@Inject
IndexingConfig indexingConfig;

@Inject
ReferenceService referenceService;

private final AtomicBoolean reindexingInProgress = new AtomicBoolean();

void registerManagementRoutes(@Observes ManagementInterface mi) {
Expand Down Expand Up @@ -237,6 +241,7 @@ private void indexAll(FailureCollector failureCollector) {
searchMapping.scope(Object.class).workspace().refresh();

rollover.commit();
referenceService.invalidateCaches();
Log.info("Indexing success");
} catch (RuntimeException | IOException e) {
throw new IllegalStateException("Failed to index data: " + e.getMessage(), e);
Expand Down
67 changes: 67 additions & 0 deletions src/test/java/io/quarkus/search/app/ReferenceServiceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package io.quarkus.search.app;

import static io.restassured.RestAssured.when;
import static org.assertj.core.api.Assertions.assertThat;

import java.util.List;

import io.quarkus.search.app.testsupport.QuarkusIOSample;
import io.quarkus.search.app.testsupport.SetupUtil;

import io.quarkus.test.common.http.TestHTTPEndpoint;
import io.quarkus.test.junit.QuarkusTest;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;

import io.restassured.RestAssured;
import io.restassured.common.mapper.TypeRef;
import io.restassured.filter.log.LogDetail;

@QuarkusTest
@TestHTTPEndpoint(SearchService.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@QuarkusIOSample.Setup
class ReferenceServiceTest {
private static final TypeRef<List<String>> LIST_OF_STRINGS = new TypeRef<>() {
};

private List<String> get(String referenceName) {
return when().get("/" + referenceName)
.then()
.statusCode(200)
.extract().body().as(LIST_OF_STRINGS);
}

@BeforeAll
void setup() {
SetupUtil.waitForIndexing(getClass());
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(LogDetail.BODY);
}

@Test
void versions() {
assertThat(get("versions")).containsExactly("main", "latest", "3.2");
}

@Test
void languages() {
assertThat(get("languages")).containsExactly("en", "es", "pt", "cn", "ja");
}

@Test
void categories() {
assertThat(get("categories")).containsExactly(
"alt-languages",
"architecture",
"cloud",
"compatibility",
"core",
"data",
"miscellaneous",
"security",
"web",
"writing-extensions");
}
}

0 comments on commit bffa25c

Please sign in to comment.