diff --git a/src/main/java/io/quarkus/search/app/SearchService.java b/src/main/java/io/quarkus/search/app/SearchService.java index af3f78ad..47eb6b76 100644 --- a/src/main/java/io/quarkus/search/app/SearchService.java +++ b/src/main/java/io/quarkus/search/app/SearchService.java @@ -32,20 +32,23 @@ public class SearchService { public SearchResult search(@RestQuery String q) { var result = session.search(Guide.class) .select(SearchHit.class) - .where((f, root) -> { - root.add(f.matchAll()); // Default - if (q != null && !q.isBlank()) { - root.add(f.simpleQueryString() - .field("keywords").boost(10.0f) - .field("title").boost(7.0f) - .field("summary").boost(5.0f) - .field("fullContent") - .field("keywords_autocomplete").boost(1.0f) - .field("title_autocomplete").boost(0.7f) - .field("summary_autocomplete").boost(0.5f) - .field("fullContent_autocomplete").boost(0.1f) - .matching(q)); + .where(f -> { + if (q == null || q.isBlank()) { + return f.matchAll(); } + + return f.bool().must(f.simpleQueryString() + .field("title").boost(10.0f) + .field("topics").boost(10.0f) + .field("keywords").boost(10.0f) + .field("summary").boost(5.0f) + .field("fullContent") + .field("keywords_autocomplete").boost(1.0f) + .field("title_autocomplete").boost(1.0f) + .field("summary_autocomplete").boost(0.5f) + .field("fullContent_autocomplete").boost(0.1f) + .matching(q)) + .should(f.not(f.match().field("topics").matching("compatibility")).boost(50.0f)); }) .sort(f -> f.score().then().field("title_sort")) .fetch(20); diff --git a/src/main/java/io/quarkus/search/app/entity/Guide.java b/src/main/java/io/quarkus/search/app/entity/Guide.java index e4606065..508b2324 100644 --- a/src/main/java/io/quarkus/search/app/entity/Guide.java +++ b/src/main/java/io/quarkus/search/app/entity/Guide.java @@ -4,7 +4,11 @@ import jakarta.persistence.Entity; import jakarta.persistence.Id; +import java.util.Set; + import org.hibernate.Length; +import org.hibernate.search.engine.backend.types.Aggregable; +import org.hibernate.search.engine.backend.types.Projectable; import org.hibernate.search.engine.backend.types.Searchable; import org.hibernate.search.engine.backend.types.Sortable; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField; @@ -42,6 +46,13 @@ public class Guide { // Using PathWrapper because of https://hibernate.atlassian.net/browse/HSEARCH-4988 public PathWrapper fullContentPath; + @FullTextField(name = "topics") + @KeywordField(name = "topics_faceting", searchable = Searchable.YES, projectable = Projectable.YES, aggregable = Aggregable.YES) + public Set topics; + + @KeywordField(name = "extensions_faceting", searchable = Searchable.YES, projectable = Projectable.YES, aggregable = Aggregable.YES) + public Set extensions; + @Override public String toString() { return "Guide{" + diff --git a/src/main/java/io/quarkus/search/app/fetching/QuarkusIO.java b/src/main/java/io/quarkus/search/app/fetching/QuarkusIO.java index 992f748b..2d4eaeaf 100644 --- a/src/main/java/io/quarkus/search/app/fetching/QuarkusIO.java +++ b/src/main/java/io/quarkus/search/app/fetching/QuarkusIO.java @@ -3,7 +3,11 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; +import java.util.HashSet; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.commons.io.FilenameUtils; @@ -40,8 +44,19 @@ private Guide parseGuide(Path path) { guide.fullContentPath = new PathWrapper(path); Asciidoc.parse(path, title -> guide.title = title, Map.of("summary", summary -> guide.summary = summary, - "keywords", keywords -> guide.keywords = keywords)); + "keywords", keywords -> guide.keywords = keywords, + "topics", topics -> guide.topics = toSet(topics), + "extensions", extensions -> guide.extensions = toSet(extensions))); return guide; } + private static Set toSet(String value) { + if (value == null || value.isBlank()) { + return Set.of(); + } + + return Arrays.stream(value.split(",")) + .map(String::trim) + .collect(Collectors.toCollection(HashSet::new)); + } } diff --git a/src/test/data/quarkusio/_guides/duplicated-context.adoc b/src/test/data/quarkusio/_guides/duplicated-context.adoc index 25a97d14..ae092149 100644 --- a/src/test/data/quarkusio/_guides/duplicated-context.adoc +++ b/src/test/data/quarkusio/_guides/duplicated-context.adoc @@ -8,6 +8,7 @@ https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc include::_attributes.adoc[] :diataxis-type: concept :categories: core, architecture +:topics: internals,extensions When using a traditional, blocking, and synchronous framework, processing of each request is performed in a dedicated thread. So, the same thread is used for the entire processing. @@ -247,4 +248,4 @@ The OpenTelemetry extension stores the traces in the duplicated context, ensurin - xref:quarkus-reactive-architecture.adoc[Quarkus Reactive Architecture] - https://vertx.io/docs/vertx-core/java/#_reactor_and_multi_reactor[Vert.x Reactor and Multi-Reactor documentation] -- xref:logging.adoc[Quarkus logging] +- xref:logging.adoc[Quarkus logging] \ No newline at end of file diff --git a/src/test/data/quarkusio/_guides/hibernate-orm-panache-kotlin.adoc b/src/test/data/quarkusio/_guides/hibernate-orm-panache-kotlin.adoc index 76251540..8a45bc59 100644 --- a/src/test/data/quarkusio/_guides/hibernate-orm-panache-kotlin.adoc +++ b/src/test/data/quarkusio/_guides/hibernate-orm-panache-kotlin.adoc @@ -8,13 +8,15 @@ include::_attributes.adoc[] :categories: data, alt-languages :summary: This explains the specifics of using Hibernate ORM with Panache in a Kotlin project. :config-file: application.properties +:topics: data,hibernate-orm,panache,kotlin,sql,jdbc +:extensions: io.quarkus:quarkus-hibernate-orm-panache-kotlin,io.quarkus:quarkus-hibernate-orm-panache,io.quarkus:quarkus-hibernate-orm Hibernate ORM is the de facto standard Jakarta Persistence (formerly known as JPA) implementation and is well-known in the Java ecosystem. Hibernate ORM with Panache offers a new layer atop this familiar framework. This guide will not dive in to the specifics of either as those are already covered in the xref:hibernate-orm-panache.adoc[Hibernate ORM with Panache guide]. In this guide, we'll cover the Kotlin specific changes needed to use Hibernate ORM with Panache in your Kotlin-based Quarkus applications. -NOTE: When using the kotlin version of Hibernate ORM with Panache, note that the `PanacheEntity`, `PanacheQuery` and `PanacheRepository` are in a different package: `io.quarkus.hibernate.orm.panache.kotlin`. +NOTE: When using the Kotlin version of Hibernate ORM with Panache, note that the `PanacheEntity`, `PanacheQuery` and `PanacheRepository` are in a different package: `io.quarkus.hibernate.orm.panache.kotlin`. == First: an example diff --git a/src/test/data/quarkusio/_guides/hibernate-orm-panache.adoc b/src/test/data/quarkusio/_guides/hibernate-orm-panache.adoc index a26c101f..6377bc90 100644 --- a/src/test/data/quarkusio/_guides/hibernate-orm-panache.adoc +++ b/src/test/data/quarkusio/_guides/hibernate-orm-panache.adoc @@ -8,6 +8,8 @@ include::_attributes.adoc[] :categories: data :summary: Hibernate ORM is the de facto Jakarta Persistence implementation and offers you the full breadth of an Object Relational Mapper. It makes complex mappings possible, but it does not make simple and common mappings trivial. Panache focuses on making your entities trivial and fun to write. :config-file: application.properties +:topics: data,hibernate-orm,panache,sql,jdbc +:extensions: io.quarkus:quarkus-hibernate-orm-panache,io.quarkus:quarkus-hibernate-orm Hibernate ORM is the de facto Jakarta Persistence (formerly known as JPA) implementation and offers you the full breadth of an Object Relational Mapper. It makes complex mappings possible, but it does not make simple and common mappings trivial. @@ -944,7 +946,7 @@ matching the values returned by the select clause: ---- import io.quarkus.runtime.annotations.RegisterForReflection; -@RegisterForReflection +@RegisterForReflection public class RaceWeight { public final String race; public final Double weight; @@ -1198,7 +1200,7 @@ public class PanacheFunctionalityTest { // We can even mock your custom methods Mockito.when(Person.findOrdered()).thenReturn(Collections.emptyList()); Assertions.assertTrue(Person.findOrdered().isEmpty()); - + // Mocking a void method Person.voidMethod(); diff --git a/src/test/data/quarkusio/_guides/hibernate-orm.adoc b/src/test/data/quarkusio/_guides/hibernate-orm.adoc index 48933c07..c76ac589 100644 --- a/src/test/data/quarkusio/_guides/hibernate-orm.adoc +++ b/src/test/data/quarkusio/_guides/hibernate-orm.adoc @@ -8,6 +8,8 @@ include::_attributes.adoc[] :categories: data :summary: Hibernate ORM is the de facto Jakarta Persistence implementation and offers you the full breath of an Object Relational Mapper. It works beautifully in Quarkus. :config-file: application.properties +:topics: data,hibernate-orm,sql,jdbc +:extensions: io.quarkus:quarkus-hibernate-orm Hibernate ORM is the de facto standard Jakarta Persistence (formerly known as JPA) implementation and offers you the full breadth of an Object Relational Mapper. It works beautifully in Quarkus. @@ -97,12 +99,12 @@ They will often map to Hibernate ORM configuration properties but could have dif Also, Quarkus will set many Hibernate ORM configuration settings automatically, and will often use more modern defaults. -For a list of the items that you can set in `{config-file}`, see xref:hibernate-configuration-properties[Hibernate ORM configuration properties]. +For a list of the items that you can set in `{config-file}`, see <>. An `EntityManagerFactory` will be created based on the Quarkus `datasource` configuration as long as the Hibernate ORM extension is listed among your project dependencies. The dialect will be selected and configured automatically based on your datasource; -you may want to xref:hibernate-dialect[configure it to more precisely match your database]. +you may want to <>. You can then happily inject your `EntityManager`: @@ -234,7 +236,7 @@ In that case, keep in mind that the JDBC driver or Hibernate ORM dialect may not work properly in GraalVM native executables. ==== -As with xref:hibernate-dialect-supported-databases[supported databases], +As with <>, you can configure the DB version explicitly to get the most out of Hibernate ORM: [source,properties] @@ -268,7 +270,7 @@ include::{generated-dir}/config/quarkus-hibernate-orm.adoc[opts=optional, levelo [NOTE] ==== -Do not mix xref:persistence-xml[`persistence.xml`] and `quarkus.hibernate-orm.*` properties in `{config-file}`. +Do not mix <> and `quarkus.hibernate-orm.*` properties in `{config-file}`. Quarkus will raise an exception. Make up your mind on which approach you want to use. @@ -537,7 +539,7 @@ the https://jakarta.ee/specifications/persistence/3.0/jakarta-persistence-spec-3 or the http://hibernate.org/dtd/hibernate-mapping-3.0.dtd[`hbm.xml` format (specific to Hibernate ORM, deprecated)]: * in `application.properties` through the (build-time) link:#quarkus-hibernate-orm_quarkus.hibernate-orm.mapping-files[`quarkus.hibernate-orm.mapping-files`] property. -* in xref:persistence-xml[`persistence.xml`] through the `` element. +* in <> through the `` element. XML mapping files are parsed at build time. @@ -856,13 +858,13 @@ configure your datasource as in the above examples and it will set up Hibernate More details about this connection pool can be found in xref:datasource.adoc[Quarkus - Datasources]. Second Level Cache:: -As explained earlier in the xref:caching[Caching section], you don't need to pick an implementation. +As explained earlier in the <>, you don't need to pick an implementation. A suitable implementation based on technologies from link:https://infinispan.org/[Infinispan] and link:https://github.com/ben-manes/caffeine[Caffeine] is included as a transitive dependency of the Hibernate ORM extension, and automatically integrated during the build. === Limitations XML mapping with duplicate files in the classpath:: -xref:xml-mapping[XML mapping] files are expected to have a unique path. +<> files are expected to have a unique path. + In practice, it's only possible to have duplicate XML mapping files in the classpath in very specific scenarios. For example, if two JARs include a `META-INF/orm.xml` file (with the exact same path but in different JARs), @@ -944,7 +946,7 @@ public class FruitResource { return entityManager.createNamedQuery("Fruits.findAll", Fruit.class) .getResultList().toArray(new Fruit[0]); } - + } ---- @@ -968,7 +970,7 @@ public class CustomTenantResolver implements TenantResolver { public String getDefaultTenantId() { return "base"; } - + @Override public String resolveTenantId() { String path = context.request().path(); @@ -981,13 +983,13 @@ public class CustomTenantResolver implements TenantResolver { return parts[1]; } - + } ---- <1> Annotate the TenantResolver implementation with the `@PersistenceUnitExtension` qualifier to tell Quarkus it should be used in the default persistence unit. + -For xref:multiple-persistence-units[named persistence units], use `@PersistenceUnitExtension("nameOfYourPU")`. +For <>, use `@PersistenceUnitExtension("nameOfYourPU")`. <2> The bean is made `@RequestScoped` as the tenant resolution depends on the incoming request. From the implementation above, tenants are resolved from the request path so that in case no tenant could be inferred, the default tenant identifier is returned. @@ -1026,7 +1028,7 @@ The following setup will use the xref:flyway.adoc[Flyway] extension to achieve t ==== SCHEMA approach The same data source will be used for all tenants and a schema has to be created for every tenant inside that data source. -CAUTION: Some databases like MariaDB/MySQL do not support database schemas. In these cases you have to use the DATABASE approach below. +CAUTION: Some databases like MariaDB/MySQL do not support database schemas. In these cases you have to use the DATABASE approach below. [source,properties] ---- @@ -1035,7 +1037,7 @@ quarkus.hibernate-orm.database.generation=none # Enable SCHEMA approach and use default datasource quarkus.hibernate-orm.multitenant=SCHEMA -# You could use a non-default datasource by using the following setting +# You could use a non-default datasource by using the following setting # quarkus.hibernate-orm.multitenant-schema-datasource=other # The default data source used for all tenant schemas @@ -1081,7 +1083,7 @@ INSERT INTO mycompany.known_fruits(id, name) VALUES (3, 'Blackberries'); ==== DATABASE approach -For every tenant you need to create a named data source with the same identifier that is returned by the `TenantResolver`. +For every tenant you need to create a named data source with the same identifier that is returned by the `TenantResolver`. [source,properties] ---- @@ -1171,7 +1173,7 @@ quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/quarkus_test If you need a more dynamic configuration for the different tenants you want to support and don't want to end up with multiple entries in your configuration file, you can use the `io.quarkus.hibernate.orm.runtime.tenant.TenantConnectionResolver` interface to implement your own logic for retrieving a connection. Creating an application-scoped bean that implements this interface -and annotating it with `@PersistenceUnitExtension` (or `@PersistenceUnitExtension("nameOfYourPU")` for a xref:multiple-persistence-units[named persistence unit]) +and annotating it with `@PersistenceUnitExtension` (or `@PersistenceUnitExtension("nameOfYourPU")` for a <>) will replace the current Quarkus default implementation `io.quarkus.hibernate.orm.runtime.tenant.DataSourceTenantConnectionResolver`. Your custom connection resolver would allow for example to read tenant information from a database and create a connection per tenant at runtime based on it. @@ -1196,7 +1198,7 @@ public static class MyInterceptor extends EmptyInterceptor { // <2> <1> Annotate the interceptor implementation with the `@PersistenceUnitExtension` qualifier to tell Quarkus it should be used in the default persistence unit. + -For xref:multiple-persistence-units[named persistence units], use `@PersistenceUnitExtension("nameOfYourPU")` +For <>, use `@PersistenceUnitExtension("nameOfYourPU")` <2> Either extend `org.hibernate.EmptyInterceptor` or implement `org.hibernate.Interceptor` directly. <3> Implement methods as necessary. @@ -1238,5 +1240,5 @@ public class MyStatementInspector implements StatementInspector { // <2> <1> Annotate the statement inspector implementation with the `@PersistenceUnitExtension` qualifier to tell Quarkus it should be used in the default persistence unit. + -For xref:multiple-persistence-units[named persistence units], use `@PersistenceUnitExtension("nameOfYourPU")` +For <>, use `@PersistenceUnitExtension("nameOfYourPU")` <2> Implement `org.hibernate.engine.jdbc.spi.StatementInspector`. diff --git a/src/test/data/quarkusio/_guides/hibernate-reactive-panache.adoc b/src/test/data/quarkusio/_guides/hibernate-reactive-panache.adoc index 7ba55fe4..37ce5172 100644 --- a/src/test/data/quarkusio/_guides/hibernate-reactive-panache.adoc +++ b/src/test/data/quarkusio/_guides/hibernate-reactive-panache.adoc @@ -8,6 +8,8 @@ include::_attributes.adoc[] :categories: data :summary: Simplified reactive ORM layer based on Hibernate Reactive. :config-file: application.properties +:topics: data,hibernate-reactive,panache,sql +:extensions: io.quarkus:quarkus-hibernate-reactive-panache,io.quarkus:quarkus-hibernate-reactive link:https://hibernate.org/reactive/[Hibernate Reactive] is the only reactive Jakarta Persistence (formerly known as JPA) implementation and offers you the full breadth of an Object Relational Mapper allowing you to access your database over reactive drivers. diff --git a/src/test/data/quarkusio/_guides/hibernate-reactive.adoc b/src/test/data/quarkusio/_guides/hibernate-reactive.adoc index 61553983..21283817 100644 --- a/src/test/data/quarkusio/_guides/hibernate-reactive.adoc +++ b/src/test/data/quarkusio/_guides/hibernate-reactive.adoc @@ -8,6 +8,8 @@ include::_attributes.adoc[] :config-file: application.properties :reactive-doc-url-prefix: https://hibernate.org/reactive/documentation/1.1/reference/html_single/#getting-started :extension-status: preview +:topics: data,hibernate-reactive,panache,sql +:extensions: io.quarkus:quarkus-hibernate-reactive-panache,io.quarkus:quarkus-hibernate-reactive link:https://hibernate.org/reactive/[Hibernate Reactive] is a reactive API for Hibernate ORM, supporting non-blocking database drivers and a reactive style of interaction with the database. @@ -114,7 +116,7 @@ Also, Quarkus will set many Hibernate Reactive configuration settings automatica WARNING: Configuring Hibernate Reactive using the standard `persistence.xml` configuration file is not supported. -See section xref:hr-configuration-properties[Hibernate Reactive configuration properties] for the list of properties you can set in `{config-file}`. +See section <> for the list of properties you can set in `{config-file}`. A `Mutiny.SessionFactory` will be created based on the Quarkus `datasource` configuration as long as the Hibernate Reactive extension is listed among your project dependencies. @@ -301,12 +303,6 @@ it's not possible to configure multiple persistence units, or even a single named persistence unit. * This extension cannot be used at the same time as Hibernate ORM. See https://github.com/quarkusio/quarkus/issues/13425. -* This extension cannot be used at the same time as JDBC/Agroal. -See https://github.com/quarkusio/quarkus/issues/33380. -+ -This also means this extension cannot be used at the same time as extensions that rely on JDBC, -such as the Flyway extension. -See https://github.com/quarkusio/quarkus/issues/10716. * Integration with the Envers extension is not supported. * Transaction demarcation cannot be done using `jakarta.transaction.Transactional` or `QuarkusTransaction`; if you use xref:hibernate-reactive-panache.adoc[Hibernate Reactive with Panache], diff --git a/src/test/data/quarkusio/_guides/hibernate-search-orm-elasticsearch.adoc b/src/test/data/quarkusio/_guides/hibernate-search-orm-elasticsearch.adoc index 7e5a2757..f0e999d8 100644 --- a/src/test/data/quarkusio/_guides/hibernate-search-orm-elasticsearch.adoc +++ b/src/test/data/quarkusio/_guides/hibernate-search-orm-elasticsearch.adoc @@ -7,6 +7,8 @@ https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc include::_attributes.adoc[] :categories: data :summary: Hibernate Search allows you to index your entities in an Elasticsearch cluster and easily offer full text search in all your Hibernate ORM-based applications. +:topics: data,hibernate-search,elasticsearch,search,nosql +:extensions: io.quarkus:quarkus-hibernate-search-orm-elasticsearch You have a Hibernate ORM-based application? You want to provide a full-featured full-text search to your users? You're at the right place. @@ -558,7 +560,7 @@ In our existing `LibraryResource`, we just need to inject the `SearchSession`: <1> Inject a Hibernate Search session, which relies on the `EntityManager` under the hood. Applications with multiple persistence units can use the CDI qualifier `@io.quarkus.hibernate.orm.PersistenceUnit` to select the right one: -see xref:multiple-persistence-units-attaching-cdi[CDI integration]. +see <>. And then we can add the following methods (and a few ``import``s): @@ -641,11 +643,11 @@ quarkus.hibernate-search-orm.indexing.plan.synchronization.strategy=sync <5> <4> We need to tell Hibernate Search about the version of Elasticsearch we will use. It is important because there are significant differences between Elasticsearch mapping syntax depending on the version. Since the mapping is created at build time to reduce startup time, Hibernate Search cannot connect to the cluster to automatically detect the version. -Note that, for OpenSearch, you need to prefix the version with `opensearch:`; see xref:opensearch[OpenSearch compatibility]. +Note that, for OpenSearch, you need to prefix the version with `opensearch:`; see <>. <5> This means that we wait for the entities to be searchable before considering a write complete. On a production setup, the `write-sync` default will provide better performance. Using `sync` is especially important when testing as you need the entities to be searchable immediately. -<6> For development and tests, we rely on xref:dev-services[Dev Services], +<6> For development and tests, we rely on <>, which means Quarkus will start a PostgreSQL database and Elasticsearch cluster automatically. In production mode, however, you will want to start a PostgreSQL database and Elasticsearch cluster manually, @@ -653,7 +655,7 @@ which is why we provide Quarkus with this connection info in the `prod` profile [NOTE] ==== -Because we rely on xref:dev-services[Dev Services], the database and Elasticsearch schema +Because we rely on <>, the database and Elasticsearch schema will automatically be dropped and re-created on each application startup in tests and dev mode (unless link:#quarkus-hibernate-search-orm-elasticsearch_quarkus.hibernate-search-orm.schema-management.strategy[`quarkus.hibernate-search-orm.schema-management.strategy`] is set explicitly). @@ -674,7 +676,7 @@ See also link:#quarkus-hibernate-search-orm-elasticsearch_quarkus.hibernate-sear [TIP] -For more information about the Hibernate Search extension configuration please refer to the xref:configuration-reference[Configuration Reference]. +For more information about the Hibernate Search extension configuration please refer to the <>. [[dev-services]] === Dev Services (Configuration Free Databases) @@ -1002,7 +1004,7 @@ For more information about coordination in Hibernate Search, see link:{hibernate-search-docs-url}#coordination[this section of the reference documentation]. For more information about configuration options related to coordination, -see xref:configuration-reference-coordination-outbox-polling[Configuration of coordination with outbox polling]. +see <>. [[aws-request-signing]] == [[configuration-reference-aws]] AWS request signing @@ -1055,7 +1057,7 @@ For example `class:com.mycompany.MyClass`. * An arbitrary string referencing a built-in implementation. Available values are detailed in the documentation of each configuration property, such as `async`/`read-sync`/`write-sync`/`sync` for -xref:quarkus-hibernate-search-orm-elasticsearch_quarkus.hibernate-search-orm.indexing.plan.synchronization.strategy[`quarkus.hibernate-search-orm.indexing.plan.synchronization.strategy`]. +<>. Other formats are also accepted, but are only useful for advanced use cases. See link:{hibernate-search-docs-url}#configuration-bean-reference-parsing[this section of Hibernate Search's reference documentation] @@ -1066,6 +1068,6 @@ for more information. [[configuration-reference-coordination-outbox-polling]] === Configuration of coordination with outbox polling -NOTE: These configuration properties require an additional extension. See xref:coordination[Coordination through outbox polling]. +NOTE: These configuration properties require an additional extension. See <>. include::{generated-dir}/config/quarkus-hibernate-search-orm-coordination-outboxpolling.adoc[leveloffset=+1, opts=optional] diff --git a/src/test/data/quarkusio/_guides/security-oidc-bearer-token-authentication.adoc b/src/test/data/quarkusio/_guides/security-oidc-bearer-token-authentication.adoc index eb4ba157..b89f012d 100644 --- a/src/test/data/quarkusio/_guides/security-oidc-bearer-token-authentication.adoc +++ b/src/test/data/quarkusio/_guides/security-oidc-bearer-token-authentication.adoc @@ -8,6 +8,8 @@ https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc include::_attributes.adoc[] :diataxis-type: concept :categories: security,web +:topics: security,oidc,bearer-token,keycloak,authentication +:extensions: io.quarkus:quarkus-oidc Secure HTTP access to Jakarta REST (formerly known as JAX-RS) endpoints in your application with Bearer token authentication by using the Quarkus OpenID Connect (OIDC) extension. @@ -96,6 +98,7 @@ The current tenant's discovered link:https://openid.net/specs/openid-connect-dis The default tenant's `OidcConfigurationMetadata` is injected if the endpoint is public. +[[token-claims-and-security-identity-roles]] === Token Claims And SecurityIdentity Roles SecurityIdentity roles can be mapped from the verified JWT access tokens as follows: @@ -112,6 +115,50 @@ If UserInfo is the source of the roles then set `quarkus.oidc.authentication.use Additionally, a custom `SecurityIdentityAugmentor` can also be used to add the roles as documented in xref:security-customization.adoc#security-identity-customization[Security Identity Customization]. +[[token-scopes-and-security-identity-permissions]] +=== Token scopes And SecurityIdentity permissions + +SecurityIdentity permissions are mapped in the form of the `io.quarkus.security.StringPermission` from the scope parameter of the <>, using the same claim separator. + +[source, java] +---- +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import org.eclipse.microprofile.jwt.Claims; +import org.eclipse.microprofile.jwt.JsonWebToken; + +import io.quarkus.security.PermissionsAllowed; + +@Path("/service") +public class ProtectedResource { + + @Inject + JsonWebToken accessToken; + + @PermissionsAllowed("email") <1> + @GET + @Path("/email") + public Boolean isUserEmailAddressVerifiedByUser() { + return accessToken.getClaim(Claims.email_verified.name()); + } + + @PermissionsAllowed("orders_read") <2> + @GET + @Path("/order") + public List listOrders() { + return List.of(new Order(1)); + } + +} +---- +<1> Only requests with OpenID Connect scope `email` are going to be granted access. +<2> The read access is limited to the client requests with scope `orders_read`. + +Please refer to the Permission annotation section of the xref:security-authorize-web-endpoints-reference.adoc#permission-annotation[Authorization of web endpoints] +guide for more information about the `io.quarkus.security.PermissionsAllowed` annotation. + [[token-verification-introspection]] === Token Verification And Introspection @@ -470,6 +517,70 @@ public class CustomOidcWireMockStubTest { } ---- +[[integration-testing-oidc-test-client]] +=== OidcTestClient + +If you work with SaaS OIDC providers such as `Auth0` and would like to run tests against the test (development) domain or prefer to run tests against a remote Keycloak test realm, when you already have `quarkus.oidc.auth-server-url` configured, you can use `OidcTestClient`. + +For example, lets assume you have the following configuration: + +[source,properties] +---- +%test.quarkus.oidc.auth-server-url=https://dev-123456.eu.auth0.com/ +%test.quarkus.oidc.client-id=test-auth0-client +%test.quarkus.oidc.credentials.secret=secret +---- + +Start with addding the same dependency as in the <> section, `quarkus-test-oidc-server`. + +Next, write the test code like this: + +[source, java] +---- +package org.acme; + +import org.junit.jupiter.api.AfterAll; +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.is; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.oidc.client.OidcTestClient; + +@QuarkusTest +public class GreetingResourceTest { + + static OidcTestClient oidcTestClient = new OidcTestClient(); + + @AfterAll + public static void close() { + client.close(); + } + + @Test + public void testHelloEndpoint() { + given() + .auth().oauth2(getAccessToken("alice", "alice")) + .when().get("/hello") + .then() + .statusCode(200) + .body(is("Hello, Alice")); + } + + private String getAccessToken(String name, String secret) { + return oidcTestClient.getAccessToken(name, secret, + Map.of("audience", "https://dev-123456.eu.auth0.com/api/v2/", + "scope", "profile")); + } +} +---- + +This test code acquires a token using a `password` grant from the test `Auth0` domain which has an application with the client id `test-auth0-client` registered, and which has a user `alice` with a password `alice` created. The test `Auth0` application must have the `password` grant enabled for a test like this one to work. This example code also shows how to pass additional parameters. For `Auth0`, these are the `audience` and `scope` parameters. + + [[integration-testing-keycloak-devservices]] ==== Dev Services for Keycloak @@ -565,7 +676,7 @@ Please see xref:security-openid-connect-dev-services.adoc[Dev Services for Keycl [[integration-testing-keycloak]] ==== KeycloakTestResourceLifecycleManager -If you need to do some integration testing against Keycloak then you are encouraged to do it with xref:integration-testing-keycloak-devservices[Dev Services For Keycloak]. +If you need to do some integration testing against Keycloak then you are encouraged to do it with <>. Use `KeycloakTestResourceLifecycleManager` for your tests only if there is a good reason not to use `Dev Services for Keycloak`. Start with adding the following dependency: diff --git a/src/test/data/quarkusio/_guides/security-vulnerability-detection.adoc b/src/test/data/quarkusio/_guides/security-vulnerability-detection.adoc deleted file mode 100644 index 303348de..00000000 --- a/src/test/data/quarkusio/_guides/security-vulnerability-detection.adoc +++ /dev/null @@ -1,139 +0,0 @@ -//// -This document is maintained in the main Quarkus repository -and pull requests should be submitted there: -https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc -//// -[id="security-vulnerability-detection"] -= Security vulnerability detection and reporting in Quarkus -include::_attributes.adoc[] -:diataxis-type: concept -:categories: security,contributing - -Most of the Quarkus tags are registered in the US link:https://nvd.nist.gov[National Vulnerability Database (NVD)] in Common Platform Enumeration (CPE) name format. - -== US National Vulnerability Database - -To view the registered Quarkus CPE names in the US NVD, use the following search URL: - -https://nvd.nist.gov/products/cpe/search/results?namingFormat=2.3&keyword=quarkus - -If the NVD database flags a CVE against a Quarkus tag, a link that provides more details about the CVE is added to the given CPE name entry. - -The NVD CPE team updates the list regularly, but if you encounter a false positive, report the details by creating an issue in the link:https://github.com/quarkusio/quarkus/issues/2611[quarkusio] repository. - -== Detect vulnerabilities in Quarkus at build time - -You can detect the vulnerabilities at the application build time with an NVD feed by using the Maven link:https://jeremylong.github.io/DependencyCheck/dependency-check-maven/[OWASP Dependency-check-maven plugin]. - - -To add the Open Worldwide Application Security Project (OWASP) Dependency-check-maven plugin to your Quarkus Maven project, add the following XML configuration to the `pom.xml` file: - -[source,xml] ----- - - org.owasp - dependency-check-maven - ${owasp-dependency-check-plugin.version} - ----- - -[IMPORTANT] -==== -Set the `owasp-dependency-check-plugin.version` value to `8.3.1` or later. -==== - -Next, configure the plugin as follows: - -[source,xml] ----- - - org.owasp - dependency-check-maven - ${owasp-dependency-check-plugin.version} - - - 7 - - ${project.basedir}/dependency-cpe-suppression.xml - - - ----- - -To detect less severe issues, adjust the value of `failBuildOnCVSS` to suppress the false positives, as demonstrated in the following code sample: -[source,xml] ----- - - - - - - - - ^io\.netty:netty-tcnative-classes.*:.*$ - cpe:/a:netty:netty - - - - - - ^io\.quarkus:quarkus-mutiny.*:.*$ - cpe:/a:mutiny:mutiny - - - - - - ^io\.smallrye\.reactive:mutiny.*:.*$ - cpe:/a:mutiny:mutiny - - - - - - ^io\.smallrye\.reactive:smallrye-mutiny.*:.*$ - cpe:/a:mutiny:mutiny - - - - - - ^io\.smallrye\.reactive:vertx-mutiny.*:.*$ - cpe:/a:mutiny:mutiny - - - - - - ^org\.graalvm\.sdk:graal-sdk.*:.*$ - - ----- - -Ensure that you review and update the suppression list regularly to ensure that the results are up to date. -You can optionally apply a time limit to individual suppressions by adding an expiry attribute, as outlined in the following example: - -`...` - -You can adjust the expiry date if you need to. - -== References - -* xref:security-overview.adoc[Quarkus Security overview] -* xref:security-authentication-mechanisms.adoc[Authentication mechanisms in Quarkus] diff --git a/src/test/data/quarkusio/_guides/spring-data-jpa.adoc b/src/test/data/quarkusio/_guides/spring-data-jpa.adoc index 627f0b39..30edcbdb 100644 --- a/src/test/data/quarkusio/_guides/spring-data-jpa.adoc +++ b/src/test/data/quarkusio/_guides/spring-data-jpa.adoc @@ -7,6 +7,8 @@ https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc include::_attributes.adoc[] :categories: compatibility :summary: While you are encouraged to use Hibernate ORM with Panache for your data layer, Quarkus provides a compatibility layer for Spring Data JPA in the form of the spring-data-jpa extension. +:topics: spring,data,hibernate-orm,jpa,compatibility +:extensions: io.quarkus:quarkus-spring-data-jpa While users are encouraged to use Hibernate ORM with Panache for Relational Database access, Quarkus provides a compatibility layer for Spring Data JPA repositories in the form of the `spring-data-jpa` extension. diff --git a/src/test/data/quarkusio/_guides/stork-reference.adoc b/src/test/data/quarkusio/_guides/stork-reference.adoc new file mode 100644 index 00000000..69a0725e --- /dev/null +++ b/src/test/data/quarkusio/_guides/stork-reference.adoc @@ -0,0 +1,74 @@ +//// +This guide is maintained in the main Quarkus repository +and pull requests should be submitted there: +https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc +//// += Stork Reference Guide +:extension-status: preview +include::_attributes.adoc[] +:categories: cloud +:topics: service-discovery,load-balancing,stork +:extensions: io.quarkus:quarkus-smallrye-stork + +This guide is the companion from the xref:stork.adoc[Stork Getting Started Guide]. +It explains the configuration and usage of SmallRye Stork integration in Quarkus. + +include::{includes}/extension-status.adoc[] + +== Supported clients + +The current integration of Stork supports: + +* the Reactive REST Client +* the gRPC clients + +Warning: The gRPC client integration does not support statistic-based load balancers. + +== Available service discovery and selection + +Check the https://smallrye.io/smallrye-stork[SmallRye Stork website] to find more about the provided service discovery and selection. + +== Using Stork in Kubernetes + +Stork provides a service discovery support for Kubernetes, which goes beyond what Kubernetes provides by default. +It looks for all the pods backing up a Kubernetes service, but instead of applying a round-robin (as Kubernetes would do), it gives you the option to select the pod using a Stork load-balancer. + +To use this feature, add the following dependency to your project: + +[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] +.pom.xml +---- + + io.smallrye.stork + stork-service-discovery-kubernetes + +---- + +[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] +.build.gradle +---- +implementation("io.smallrye.stork:stork-service-discovery-kubernetes") +---- + +For each service expected to be exposed as a Kubernetes Service, configure the lookup: + +[source, properties] +---- +quarkus.stork.my-service.service-discovery.type=kubernetes +quarkus.stork.my-service.service-discovery.k8s-namespace=my-namespace +---- + +Stork looks for the Kubernetes Service with the given name (`my-service` in the previous example) in the specified namespace. +Instead of using the Kubernetes Service IP directly and let Kubernetes handle the selection and balancing, Stork inspects the service and retrieves the list of pods providing the service. Then, it can select the instance. + +For a full example of using Stork with Kubernetes, please read the xref:stork-kubernetes.adoc[Using Stork with Kubernetes guide]. + +== Extending Stork + +Stork is extensible. +You can implement your own service discovery or service selection provider. + +To learn about custom service discovery and service selection, check: + +- https://smallrye.io/smallrye-stork/latest/service-discovery/custom-service-discovery/[Implement a custom service discover provider] +- https://smallrye.io/smallrye-stork/latest/load-balancer/custom-load-balancer/[Implement a custom service selection provider] diff --git a/src/test/java/io/quarkus/search/app/DatasetConstants.java b/src/test/java/io/quarkus/search/app/DatasetConstants.java index d4944fdf..ab3c1e10 100644 --- a/src/test/java/io/quarkus/search/app/DatasetConstants.java +++ b/src/test/java/io/quarkus/search/app/DatasetConstants.java @@ -15,24 +15,24 @@ private GuideIds() { public static final String SPRING_DATA_JPA = "/guides/spring-data-jpa"; public static final String HIBERNATE_SEARCH_ORM_ELASTICSEARCH = "/guides/hibernate-search-orm-elasticsearch"; public static final String SECURITY_OIDC_BEARER_TOKEN_AUTHENTICATION = "/guides/security-oidc-bearer-token-authentication"; - public static final String SECURITY_VULNERABILITY_DETECTION = "/guides/security-vulnerability-detection"; public static final String HIBERNATE_ORM_PANACHE = "/guides/hibernate-orm-panache"; public static final String HIBERNATE_ORM_PANACHE_KOTLIN = "/guides/hibernate-orm-panache-kotlin"; public static final String HIBERNATE_REACTIVE_PANACHE = "/guides/hibernate-reactive-panache"; public static final String HIBERNATE_ORM = "/guides/hibernate-orm"; public static final String HIBERNATE_REACTIVE = "/guides/hibernate-reactive"; + public static final String STORK_REFERENCE = "/guides/stork-reference"; public static final String[] ALL = new String[] { DUPLICATED_CONTEXT, SPRING_DATA_JPA, HIBERNATE_SEARCH_ORM_ELASTICSEARCH, SECURITY_OIDC_BEARER_TOKEN_AUTHENTICATION, - SECURITY_VULNERABILITY_DETECTION, HIBERNATE_ORM_PANACHE, HIBERNATE_ORM_PANACHE_KOTLIN, HIBERNATE_REACTIVE_PANACHE, HIBERNATE_ORM, - HIBERNATE_REACTIVE + HIBERNATE_REACTIVE, + STORK_REFERENCE }; } diff --git a/src/test/java/io/quarkus/search/app/SearchServiceTest.java b/src/test/java/io/quarkus/search/app/SearchServiceTest.java index 66cf5a7c..6e7a6453 100644 --- a/src/test/java/io/quarkus/search/app/SearchServiceTest.java +++ b/src/test/java/io/quarkus/search/app/SearchServiceTest.java @@ -118,41 +118,51 @@ public void relevance(String query, List expectedGuideIds) { private static List relevance_params() { return List.of( + // I wonder if we could use something similar to https://stackoverflow.com/a/74737474/5043585 + // to have some sort of weight in the documents and prioritize some of them + // problem will be to find the right balance because the weight would be always on + // another option could be to use the keywords to trick some searches Arguments.of("orm", List.of( - // TODO Shouldn't the ORM guide be before Panache? Hibernate Reactive before Spring Data JPA? + // TODO Shouldn't the ORM guide be before Panache? GuideIds.HIBERNATE_ORM_PANACHE, GuideIds.HIBERNATE_ORM, GuideIds.HIBERNATE_ORM_PANACHE_KOTLIN, GuideIds.HIBERNATE_REACTIVE_PANACHE, GuideIds.HIBERNATE_SEARCH_ORM_ELASTICSEARCH, - GuideIds.SPRING_DATA_JPA, - GuideIds.HIBERNATE_REACTIVE)), + GuideIds.HIBERNATE_REACTIVE, + GuideIds.SPRING_DATA_JPA)), Arguments.of("reactive", List.of( GuideIds.HIBERNATE_REACTIVE_PANACHE, GuideIds.HIBERNATE_REACTIVE, - GuideIds.DUPLICATED_CONTEXT, // TODO: what is this doing here? + GuideIds.DUPLICATED_CONTEXT, // contains "Hibernate Reactive" GuideIds.HIBERNATE_ORM_PANACHE, - GuideIds.SPRING_DATA_JPA, + GuideIds.STORK_REFERENCE, GuideIds.HIBERNATE_SEARCH_ORM_ELASTICSEARCH, - GuideIds.HIBERNATE_ORM)), + GuideIds.HIBERNATE_ORM, + GuideIds.SPRING_DATA_JPA)), Arguments.of("hiber", List.of( // TODO Hibernate Reactive/Search should be after ORM... - // TODO Shouldn't the ORM guide be before Panache? Hibernate Reactive before Spring Data JPA? + // TODO Shouldn't the ORM guide be before Panache? GuideIds.HIBERNATE_SEARCH_ORM_ELASTICSEARCH, GuideIds.HIBERNATE_REACTIVE_PANACHE, GuideIds.HIBERNATE_ORM_PANACHE, + GuideIds.HIBERNATE_REACTIVE, GuideIds.HIBERNATE_ORM, GuideIds.HIBERNATE_ORM_PANACHE_KOTLIN, - GuideIds.HIBERNATE_REACTIVE, + GuideIds.DUPLICATED_CONTEXT, // contains "Hibernate Reactive" GuideIds.SPRING_DATA_JPA)), Arguments.of("jpa", List.of( - // TODO we'd probably want ORM before Spring Data JPA (and before Panache?) - GuideIds.SPRING_DATA_JPA, + // TODO we'd probably want ORM before Panache? GuideIds.HIBERNATE_ORM_PANACHE_KOTLIN, GuideIds.HIBERNATE_ORM_PANACHE, - GuideIds.HIBERNATE_REACTIVE_PANACHE, // TODO: this is odd... especially since the HR guide isn't here? - GuideIds.HIBERNATE_ORM)), + GuideIds.HIBERNATE_REACTIVE_PANACHE, // contains a reference to jpa-modelgen + GuideIds.HIBERNATE_ORM, + GuideIds.SPRING_DATA_JPA)), Arguments.of("search", List.of( - GuideIds.HIBERNATE_SEARCH_ORM_ELASTICSEARCH))); + GuideIds.HIBERNATE_SEARCH_ORM_ELASTICSEARCH)), + Arguments.of("stork", List.of( + GuideIds.STORK_REFERENCE)), + Arguments.of("spring data", List.of( + GuideIds.SPRING_DATA_JPA))); } } diff --git a/src/test/java/io/quarkus/search/app/fetching/FetchingServiceTest.java b/src/test/java/io/quarkus/search/app/fetching/FetchingServiceTest.java index af3db618..d463849f 100644 --- a/src/test/java/io/quarkus/search/app/fetching/FetchingServiceTest.java +++ b/src/test/java/io/quarkus/search/app/fetching/FetchingServiceTest.java @@ -6,6 +6,7 @@ import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Set; import java.util.function.Consumer; import jakarta.inject.Inject; @@ -15,7 +16,8 @@ import org.assertj.core.api.SoftAssertions; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; - +import org.eclipse.jgit.util.SystemReader; +import org.jboss.logging.Logger; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Nested; @@ -29,6 +31,8 @@ public class FetchingServiceTest { + private static final Logger LOG = Logger.getLogger(FetchingServiceTest.class); + // Unfortunately we can't use @TempDir here, // because we need the path initialized before we create the extension below. static Path tmpDir; @@ -84,6 +88,8 @@ public void fetchQuarkusIo() throws Exception { "Some title", "This is a summary", "keyword1, keyword2", + Set.of("topic1", "topic2"), + Set.of("io.quarkus:extension1", "io.quarkus:extension2"), FETCHED_GUIDE_CONTENT)); } } @@ -126,6 +132,8 @@ static void initOrigin() throws IOException, GitAPIException { Path guideToFetch = sourceRepoDir.resolve("_guides/" + FETCHED_GUIDE_NAME + ".adoc"); Path adocToIgnore = sourceRepoDir.resolve("_guides/_attributes.adoc"); try (Git git = Git.init().setDirectory(sourceRepoDir.toFile()).call()) { + cleanGitUserConfig(); + PathUtils.createParentDirectories(guideToFetch); Files.writeString(guideToFetch, "initial"); PathUtils.createParentDirectories(adocToIgnore); @@ -146,6 +154,8 @@ static void initOrigin() throws IOException, GitAPIException { :irrelevant: foo :keywords: keyword1, keyword2 :summary: This is a summary + :topics: topic1, topic2 + :extensions: io.quarkus:extension1,io.quarkus:extension2 This is the guide body @@ -158,13 +168,18 @@ static void initOrigin() throws IOException, GitAPIException { This is another subsection """; - private static Consumer isGuide(String relativePath, String title, String summary, String keywords, String content) { + private static Consumer isGuide(String relativePath, String title, String summary, String keywords, + Set topics, Set extensions, String content) { return guide -> { SoftAssertions.assertSoftly(softly -> { softly.assertThat(guide).extracting("relativePath").isEqualTo(relativePath); softly.assertThat(guide).extracting("title").isEqualTo(title); softly.assertThat(guide).extracting("summary").isEqualTo(summary); softly.assertThat(guide).extracting("keywords").isEqualTo(keywords); + softly.assertThat(guide).extracting("topics", InstanceOfAssertFactories.COLLECTION) + .containsExactlyInAnyOrderElementsOf(topics); + softly.assertThat(guide).extracting("extensions", InstanceOfAssertFactories.COLLECTION) + .containsExactlyInAnyOrderElementsOf(extensions); softly.assertThat(guide).extracting("fullContentPath.value", InstanceOfAssertFactories.PATH) .content() .isEqualTo(content); @@ -172,4 +187,11 @@ private static Consumer isGuide(String relativePath, String title, String }; } + private static void cleanGitUserConfig() { + try { + SystemReader.getInstance().getUserConfig().clear(); + } catch (Exception e) { + LOG.warn("Unable to clear the Git user config"); + } + } }