You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2021/04/05 13:22:49 UTC

[isis] 03/03: ISIS-2484: adds docs on jdo, entities and b2b guide

This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch 2.0.0-M5
in repository https://gitbox.apache.org/repos/asf/isis.git

commit 3696e00b76c6404e182697351a07b32b8546de34
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Mon Apr 5 14:22:27 2021 +0100

    ISIS-2484: adds docs on jdo, entities and b2b guide
---
 .../JdoSupportService_010-examples-and-usage.adoc  |  46 ++++----
 .../modules/btb/pages/headless-access.adoc         |  55 ++++++----
 .../hints-and-tips/transactions-and-errors.adoc    |   4 +-
 .../btb/pages/programming-model/finetuning.adoc    |  93 ++++++++++------
 .../pages/domain-entities-and-services/crud.adoc   | 117 +++++++++++++++++----
 .../domain-entities.adoc                           |  47 ++++++---
 .../domain-services.adoc                           |   6 +-
 .../core/interaction/session/MessageBroker.java    |   4 +
 .../typesafe-queries-and-fetchgroups.adoc          |   7 +-
 9 files changed, 260 insertions(+), 119 deletions(-)

diff --git a/antora/components/refguide-index/modules/persistence/pages/index/jdo/applib/services/hooks/JdoSupportService_010-examples-and-usage.adoc b/antora/components/refguide-index/modules/persistence/pages/index/jdo/applib/services/hooks/JdoSupportService_010-examples-and-usage.adoc
index 2d65551..5b1cf8c 100644
--- a/antora/components/refguide-index/modules/persistence/pages/index/jdo/applib/services/hooks/JdoSupportService_010-examples-and-usage.adoc
+++ b/antora/components/refguide-index/modules/persistence/pages/index/jdo/applib/services/hooks/JdoSupportService_010-examples-and-usage.adoc
@@ -4,7 +4,7 @@
 
 == Examples and Usage
 
-The `IsisJdoSupport` service provides a number of general purpose methods for working with the JDO/DataNucleus objectstore.
+The `JdoSupportService` service provides a number of general purpose methods for working with the JDO/DataNucleus objectstore.
 In general these act at a lower-level of abstraction than the APIs normally used (specifically, those of xref:refguide:applib:index/services/repository/RepositoryService.adoc[RepositoryService]), but nevertheless deal with some of the most common use cases.
 For service also provides access to the underlying JDO `PersistenceManager` for full control.
 
@@ -12,11 +12,11 @@ The following sections discuss the functionality provided by the service, broken
 
 === Executing SQL
 
-You can use the `IsisJdoSupportService` to perform arbitrary SQL SELECTs or UPDATEs:
+You can use the `JdoSupportService` to perform arbitrary SQL SELECTs or UPDATEs:
 
 [source,java]
 ----
-public interface IsisJdoSupport {
+public interface JdoSupportService {
     @Programmatic
     List<Map<String, Object>> executeSql(String sql);
     @Programmatic
@@ -29,7 +29,7 @@ The `executeSql(...)` method allows arbitrary SQL `SELECT` queries to be submitt
 
 [source,java]
 ----
-List<Map<String, Object>> results = isisJdoSupport.executeSql("select * from custMgmt.customers");
+List<Map<String, Object>> results = jdoSupportService.executeSql("select * from custMgmt.customers");
 ----
 
 The result set is automatically converted into a list of maps, where the map key is the column name.
@@ -38,14 +38,14 @@ In a similar manner, the `executeUpdate(...)` allows arbitrary SQL ``UPDATE``s t
 
 [source,java]
 ----
-int count = isisJdoSupport.executeUpdate("select count(*) from custMgmt.customers);
+int count = jdoSupportService.executeUpdate("select count(*) from custMgmt.customers);
 ----
 
 The returned value is the number of rows updated.
 
 [TIP]
 ====
-As an alternative, consider using DataNucleus' link:http://www.datanucleus.org/products/accessplatform_4_0/jdo/jdoql_typesafe.html[type-safe JDO query API], discussed xref:pjdo:ROOT:services/IsisJdoSupport.adoc#type-safe-query-api[below].
+As an alternative, consider using DataNucleus' link:http://www.datanucleus.org/products/accessplatform_4_0/jdo/jdoql_typesafe.html[type-safe JDO query API], discussed <<type-safe-jdoql-queries,below>>.
 ====
 
 [#type-safe-jdoql-queries]
@@ -65,16 +65,13 @@ Using Eclipse IDE you may need to configure annotation processing manually; see
 The DataNucleus' link:http://www.datanucleus.org/products/accessplatform_4_0/jdo/jdoql_typesafe.html[documentation] offers some guidance on confirming that APT is enabled.
 ====
 
-The `IsisJdoSupport` service offers two methods at different levels of abstraction:
+The `JdoSupportService` service offers two methods at different levels of abstraction:
 
 [source,java]
 ----
-public interface IsisJdoSupport {
-    @Programmatic
+public interface JdoSupportService {
     <T> List<T> executeQuery(final Class<T> cls, final BooleanExpression be);
-    @Programmatic
     <T> T executeQueryUnique(final Class<T> cls, final BooleanExpression be);
-    @Programmatic
     <T> TypesafeQuery<T> newTypesafeQuery(Class<T> cls);
     ...
 }
@@ -100,7 +97,7 @@ can be expressed using type-safe queries as follows:
 ----
 public List<ToDoItem> findByAtPathAndCategory(final String atPath, final Category category) {
     final QToDoItem q = QToDoItem.candidate();
-    return isisJdoSupport.executeQuery(ToDoItem.class,
+    return jdoSupportService.executeQuery(ToDoItem.class,
             q.atPath.eq(atPath).and(
             q.category.eq(category)));
 }
@@ -134,13 +131,13 @@ private static <T> List<T> executeListAndClose(final TypesafeQuery<T> query) {
 === Fixture support
 
 When writing xref:testing:integtestsupport:about.adoc[integration tests] you'll usually need to tear down some/all mutable transactional data before each test.
-One way to do that is to use the `executeUpdate(...)` method described xref:pjdo:ROOT:services/IsisJdoSupport.adoc#executing-sql[above].
+One way to do that is to use the `executeUpdate(...)` method described <<executing-sql,above>>.
 
 Alternatively, the `deleteAll(...)` method will let your test delete all instances of a class without resorting to SQL:
 
 [source,java]
 ----
-public interface IsisJdoSupport {
+public interface JdoSupportService {
     void deleteAll(Class<?>... pcClasses);
     ...
 }
@@ -153,12 +150,12 @@ For example:
 public class TearDownAll extends FixtureScriptAbstract {
     @Override
     protected void execute(final ExecutionContext ec) {
-        isisJdoSupport.deleteAll(Order.class);
-        isisJdoSupport.deleteAll(CustomerAddress.class);
-        isisJdoSupport.deleteAll(Customer.class);
+        jdoSupportService.deleteAll(Order.class);
+        jdoSupportService.deleteAll(CustomerAddress.class);
+        jdoSupportService.deleteAll(Customer.class);
     }
     @Inject
-    IsisJdoSupport isisJdoSupport;
+    JdoSupportService jdoSupportService;
 }
 ----
 
@@ -176,7 +173,7 @@ An link:http://www.datanucleus.org:15080/products/accessplatform_5_0/jdo/mapping
 
 [source,java]
 ----
-public interface IsisJdoSupport {
+public interface JdoSupportService {
     <T> T refresh(T domainObject);
     void ensureLoaded(Collection<?> collectionOfDomainObjects);
     ...
@@ -191,15 +188,14 @@ This can be valuable as a performance optimization to avoid multiple roundtrips
 Under the covers it uses the `PersistenceManager#retrieveAll(...)` API.
 
 
-=== JDO `PersistenceManager`
+=== JDO PersistenceManager
 
-The functionality provided by `IsisJdoSupport` focus only on the most common use cases.
+The functionality provided by `JdoSupportService` focus only on the most common use cases.
 If you require more flexibility than this, eg for dynamically constructed queries, then you can use the service to access the underlying JDO `PersistenceManager` API:
 
 [source,java]
 ----
-public interface IsisJdoSupport {
-    @Programmatic
+public interface JdoSupportService {
     PersistenceManager getJdoPersistenceManager();
     ...
 }
@@ -212,7 +208,7 @@ For example:
 public class Orders {
 
     public List<Order> findOrders( /* ... */ ) {
-        javax.jdo.PersistenceManager pm = isisJdoSupport.getPersistenceManager();
+        javax.jdo.PersistenceManager pm = jdoSupportService.getPersistenceManager();
 
         ...
 
@@ -220,7 +216,7 @@ public class Orders {
     }
 
     @Inject
-    IsisJdoSupport isisJdoSupport;
+    JdoSupportService jdoSupportService;
 }
 ----
 
diff --git a/antora/components/userguide/modules/btb/pages/headless-access.adoc b/antora/components/userguide/modules/btb/pages/headless-access.adoc
index 37b1586..b123ce9 100644
--- a/antora/components/userguide/modules/btb/pages/headless-access.adoc
+++ b/antora/components/userguide/modules/btb/pages/headless-access.adoc
@@ -8,35 +8,52 @@ This section tackles the topic of enabling access to an Apache Isis application
 
 There are a number of use-cases:
 
-* integration from other systems, eg for a subscriber on a pub/sub mechanism such as Camel, pushing changes through an Apache Isis domain model.
+* implementing a REST end point that needs to reach into the Apache Isis runtime
 
-* leveraging an Apache Isis application within a batch process
+** this could be a custom UI
+** this could be to integrate from other systems, eg a subscriber on a pub/sub event bus, pushing changes through an Apache Isis domain model.
+
+* calling from Quartz for a background batch process
 
 Note that the calling thread runs in the same process space as the Apache Isis domain object model (must be physically linked to the JAR files containing the domain classes).
-For use cases where the calling thread runs in some other process space (eg migrating data from a legacy system), then the xref:vro:ROOT:about.adoc[Restful Objects viewer] is usually the way to go.
 
-The key to headless access is the `IsisInteractionFactory` service, a singleton managed by Spring.
-Use it as follows:
+The xref:docs:starters:simpleapp.adoc[SimpleApp] starter app demonstrates the pattern with a custom UI controller:
 
 [source,java]
 ----
-@Service
-@lombok.RequiredArgsConstructor(onConstructor_ = { @Inject })
-private static class HeadlessAccess {
-
-    final IsisInteractionFactory isisInteractionFactory;
-    final TransactionService transactionService;
-
-    @Override
-    public R call(AuthenticationSession session, Supplier<R> supplier) {
-        return isisInteractionFactory.callAuthenticated(session, () -> {
-            return transactionService.executeWithinTransaction(() -> {
-                    return supplier.get();
-                });
-        });
+@RestController
+@RequiredArgsConstructor
+class CustomController {
+
+    private final InteractionFactory interactionFactory;
+    private final TransactionalProcessor transactionalProcessor;
+    private final SimpleObjects simpleObjects;
+
+    @GetMapping("/custom/simpleObjects")
+    List<SimpleObject> all() {
+        return callAuthenticated(newAuthentication(), () -> simpleObjects.listAll())
+                .optionalElseFail() // <.>
+                .orElse(Collections.emptyList()); // <.>
+    }
+
+    private SimpleAuthentication newAuthentication() {
+        return SimpleAuthentication.validOf(UserMemento.ofName("sven")); // <.>
+    }
+
+    private <T> Result<T> callAuthenticated(
+            final Authentication authentication,
+            final Callable<T> task) {
+
+        return interactionFactory.callAuthenticated(
+                authentication,
+                () -> transactionalProcessor
+                      .callWithinCurrentTransactionElseCreateNew(task));
     }
 }
 ----
+<.> re-throws exception that has occurred, if any
+<.> handles null case, if required
+<.> a more sophisticated implementation could inspect the HTTP request
 
 The API described here is reasonably low-level, allowing code to interact very directly with the Apache Isis metamodel and runtime.
 Such callers should be considered trusted: they do not (by default) honour any business rules eg implicit in the Isis annotations or hide/disable/validate methods.
diff --git a/antora/components/userguide/modules/btb/pages/hints-and-tips/transactions-and-errors.adoc b/antora/components/userguide/modules/btb/pages/hints-and-tips/transactions-and-errors.adoc
index cbce459..ebdb185 100644
--- a/antora/components/userguide/modules/btb/pages/hints-and-tips/transactions-and-errors.adoc
+++ b/antora/components/userguide/modules/btb/pages/hints-and-tips/transactions-and-errors.adoc
@@ -7,10 +7,10 @@
 In Apache Isis, every interaction (action invocation or property edit) is automatically wrapped in a transaction, and any repository query automatically does a flush before hand.
 
 What that means is that there's no need to explicitly start or commit transactions in Apache Isis; this will be done for you.
-Indeed, if you do try to manage transactions (eg by reaching into the JDO `PersistenceManager` exposed by the xref:pjdo:ROOT:services/IsisJdoSupport.adoc[IsisJdoSupport] domain service, then you are likely to confuse the framework and get a stack trace for your trouble.
+Indeed, if you do try to manage transactions (eg by reaching into the JDO `PersistenceManager` exposed by the xref:refguide:persistence:index/jdo/applib/services/JdoSupportService.adoc[JdoSupportService] domain service, then you are likely to confuse the framework and get a stack trace for your trouble.
 
 However, you can complete a given transaction and start a new one.
-This is sometimes useful if writing a xref:testing:fixtures:about.adoc#fixture-scripts[fixture script] which is going to perform some sort of bulk migration of data from an old system.
+This is sometimes useful if writing a xref:testing:fixtures:about.adoc[fixture script] which is going to perform some sort of bulk migration of data from an old system.
 For this use case, use the xref:refguide:applib:index/services/xactn/TransactionService.adoc[TransactionService].
 
 For example:
diff --git a/antora/components/userguide/modules/btb/pages/programming-model/finetuning.adoc b/antora/components/userguide/modules/btb/pages/programming-model/finetuning.adoc
index dc40738..9531469 100644
--- a/antora/components/userguide/modules/btb/pages/programming-model/finetuning.adoc
+++ b/antora/components/userguide/modules/btb/pages/programming-model/finetuning.adoc
@@ -4,29 +4,35 @@
 :Notice: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at. http://www.apache.org/licenses/LICENSE-2.0 . Unless required by applicable law or ag [...]
 :page-partial:
 
-WARNING: TODO - v2 - not yet reviewed/updated.
 
 The core metamodel defines APIs and implementations for building the Apache Isis metamodel: a description of the set of entities, domain services and values that make up the domain model.
 
 The description of each domain class consists of a number of elements:
 
-https://github.com/apache/isis/blob/master/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java[ObjectSpecification]::
+link:https://github.com/apache/isis/blob/master/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java[ObjectSpecification]::
+
 Analogous to `java.lang.Class`; holds information about the class itself and holds collections of each of the three types of class' members (below);
 
-https://github.com/apache/isis/blob/master/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/OneToOneAssociation.java[OneToOneAssociation]::
+
+link:https://github.com/apache/isis/blob/master/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/OneToOneAssociation.java[OneToOneAssociation]::
+
 Represents a class member that is a single-valued property of the class.
 The property's type is either a reference to another entity, or is a value type.
 
-https://github.com/apache/isis/blob/master/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/OneToManyAssociation.java[OneToManyAssociation]::
+
+link:https://github.com/apache/isis/blob/master/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/OneToManyAssociation.java[OneToManyAssociation]::
+
 Represents a class member that is a collection of references to other entities.
 Note that Apache Isis does not currently support collections of values.
 
-https://github.com/apache/isis/blob/master/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAction.java[ObjectAction]::
+
+link:https://github.com/apache/isis/blob/master/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAction.java[ObjectAction]::
+
 Represents a class member that is an operation that can be performed on the action.
 Returns either a single value, a collection of entities, or is `void`.
 
-The metamodel is built up through the https://github.com/apache/isis/blob/master/core/metamodel/src/main/java/org/apache/isis/core/metamodel/progmodel/ProgrammingModel.java[ProgrammingModel], which defines an API for registering a set of https://github.com/apache/isis/blob/master/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/FacetFactory.java[FacetFactory]s.
-Two special `FacetFactory` implementations - https://github.com/apache/isis/blob/master/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/accessor/PropertyAccessorFacetViaAccessorFactory.java[PropertyAccessorFacetFactory] and https://github.com/apache/isis/blob/master/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/collections/accessor/CollectionAccessorFacetViaAccessorFactory.java[CollectionAccessorFacetFactory] - are used to identify the c [...]
+The metamodel is built up through the link:https://github.com/apache/isis/blob/master/core/metamodel/src/main/java/org/apache/isis/core/metamodel/progmodel/ProgrammingModel.java[ProgrammingModel], which defines an API for registering a set of link:https://github.com/apache/isis/blob/master/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/FacetFactory.java[FacetFactory]s.
+Two special `FacetFactory` implementations - link:https://github.com/apache/isis/blob/master/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/accessor/PropertyAccessorFacetViaAccessorFactory.java[PropertyAccessorFacetFactory] and link:https://github.com/apache/isis/blob/master/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/collections/accessor/CollectionAccessorFacetViaAccessorFactory.java[CollectionAccessorFacetFactory] - are used to iden [...]
 Pretty much all the other ``FacetFactory``s are responsible for installing https://github.com/apache/isis/blob/master/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facetapi/Facet.java[Facet]s onto the metamodel elements.
 
 There are many many such ``Facet``s, and are used to do such things get values from properties or collections, modify properties or collections, invoke action, hide or disable class members, provide UI hints for class members, and so on.
@@ -34,39 +40,62 @@ In short, the ``FacetFactory``s registered defines the Apache Isis programming c
 
 == Modifying the Prog. Model
 
-The default implementation of `ProgrammingModel` is https://github.com/apache/isis/blob/master/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/ProgrammingModelFacetsJava5.java[ProgrammingModelFacetsJava5], which registers a large number of ``FacetFactory``s.
+Creating the `ProgrammingModel` is the responsibility of the link:https://github.com/apache/isis/blob/master/core/metamodel/src/main/java/org/apache/isis/core/metamodel/progmodel/ProgrammingModelService.java[ProgrammingModelService].
 
-By editing `application.properties` you can modify the programming conventions either by (a) using the default programming model, but tweaking it to include new `FacetFactory`s or exclude existing, or (b) by specifying a completely different programming model implementation.
+The default implementation, link:https://github.com/apache/isis/blob/master/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/ProgrammingModelServiceDefault.java[ProgrammingModelServiceDefault], creates as the `ProgrammingModel` the concrete implementation link:https://github.com/apache/isis/blob/master/core/metamodel/src/main/java/org/apache/isis/core/metamodel/progmodels/dflt/ProgrammingModelFacetsJava8.java[ProgrammingModelFacetsJava8], which registers a large num [...]
+This programming model can then be added to because the service call every known implementation of link:https://github.com/apache/isis/blob/master/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facetapi/MetaModelRefiner.java[MetaModelRefiner].
 
-Let's see how this is done.
-
-=== Including or excluding facets
-
-Suppose that you wanted to completely remove support for some existing facet, eg to support the "persisting()" and "persisted()" callback methods.
-This would be done using:
-
-xref:refguide:config:sections/isis.core.meta-model.introspector.adoc#
-
-
-[source,ini]
-----
-isis.reflector.facets.exclude=\
-    org.apache.isis.core.metamodel.facets.object.callbacks.PersistCallbackFacetFactory
-----
+The programming model can also be filtered using the link:https://github.com/apache/isis/blob/master/core/metamodel/src/main/java/org/apache/isis/core/metamodel/progmodel/ProgrammingModelInitFilter.java[ProgrammingModelInitFilter] interface.
+The default implementation of _this_ interface, link:https://github.com/apache/isis/blob/master/core/metamodel/src/main/java/org/apache/isis/core/metamodel/progmodel/ProgrammingModelInitFilterDefault.java[ProgrammingModelInitFilterDefault] accepts all facet factories though allows deprecated facet factories to be excluded through a configuration property.
 
-Or, suppose you wanted to use add some custom facet factory of your own, use:
+The diagram below shows how these classes relate:
 
-[source,ini]
+[plantuml]
+.Programming Model classes
 ----
-isis.reflector.facets.include=com.mycompany.isis.facets.MyCoolFacetFactory
+hide empty members
+
+interface ProgrammingModel {
+    init(ProgrammingModelInitFilter)
+    addFactory(...)
+    addValidator(...)
+    addPostProcessor(...)
+}
+class ProgrammingModelFacetsJava8 {}
+
+interface ProgrammingModelService {
+    getProgrammingModel(): ProgrammingModel
+}
+class ProgrammingModelServiceDefault {}
+
+interface ProgrammingModelInitFilter {
+    boolean acceptFactoryType(...);
+    boolean acceptValidator(...);
+    boolean acceptPostProcessor(...);
+}
+class ProgrammingModelInitFilterDefault {
+}
+
+interface MetaModelRefiner {
+    refineProgrammingModel(ProgrammingModel)
+}
+
+MetaModelRefiner -.[norank]r> ProgrammingModel
+ProgrammingModelService -.[norank]r> ProgrammingModel
+
+ProgrammingModelServiceDefault -> ProgrammingModelFacetsJava8 : instantiate
+ProgrammingModelServiceDefault -l-> ProgrammingModelInitFilter
+ProgrammingModelServiceDefault -d-> "0..*" MetaModelRefiner
+
+ProgrammingModel ^-d- ProgrammingModelFacetsJava8
+ProgrammingModelService ^-d- ProgrammingModelServiceDefault
+ProgrammingModelInitFilter ^-d- ProgrammingModelInitFilterDefault
 ----
 
-To include/exclude more than one `FacetFactory`, specify as a comma-separated list.
+To summarise:
 
-[TIP]
-====
-This http://isis.markmail.org/thread/472c3mrvcgnripst[thread] from the users mailing list (in Apr 2014) shows a typical customization (to enable per-instance security) (though note that xref:userguide:btb:about.adoc#multi-tenancy[Multi-Tenancy] is now a better solution to that particular use-case.
-====
+* To add to the programming model (new facet factories, validators or post processors), create a `@Service` implementing the `MetaModelRefiner` interface
+* to remove from the programming model, create a `@Service` implementing a ProgrammingModelInitFilter` (with an earlier precedence than the default implementation).
 
 
 
diff --git a/antora/components/userguide/modules/fun/pages/domain-entities-and-services/crud.adoc b/antora/components/userguide/modules/fun/pages/domain-entities-and-services/crud.adoc
index 6e5a6c6..8fb8958 100644
--- a/antora/components/userguide/modules/fun/pages/domain-entities-and-services/crud.adoc
+++ b/antora/components/userguide/modules/fun/pages/domain-entities-and-services/crud.adoc
@@ -113,12 +113,12 @@ repositoryService.persistAndFlush(customer);
 
 When an object is persisted the framework will emit `ObjectPersistingEvent` and `ObjectPersistedEvent` xref:userguide:fun:overview.adoc#lifecycle-events[lifecycle events].
 
-=== Persistence by Reachability
+=== Persistence by Reachability (JDO)
 
-It is also possible to configure ORM to automatically persist domain entities if they are associated with other already-persistent entities.
+If using xref:pjdo:ROOT:about.adoc[JDO/DataNucleus], it is possible to configure ORM to automatically persist domain entities if they are associated with other already-persistent entities.
 This avoid the need to explicitly call "persist".
 
-If using xref:pjdo:ROOT:about.adoc[JDO/DataNucleus], this is done using xref:refguide:config:sections/jdo-datanucleus-conf.adoc#datanucleus.persistenceByReachabilityAtCommit[persistence-by-reachability] configuration property:
+This is done using xref:refguide:config:sections/jdo-datanucleus-conf.adoc#datanucleus.persistenceByReachabilityAtCommit[persistence-by-reachability] configuration property:
 
 [source,ini]
 .application.properties
@@ -126,13 +126,89 @@ If using xref:pjdo:ROOT:about.adoc[JDO/DataNucleus], this is done using xref:ref
 datanucleus.persistenceByReachabilityAtCommit=true
 ----
 
-One downside is that the code is arguably less easy to debug.
+One downside is that the code is arguably less easy to debug, and there may be performance implications.
 
 
 [[finding]]
-== Finding Objects (JDO)
+== Finding Objects
+
+Retrieving domain entities depends on the ORM, though the xref:refguide:applib:index/services/repository/RepositoryService.adoc[RepositoryService] can be used as an abstraction over either if required.
+
+
+[[finding-jpa]]
+=== Finding Objects (JPA)
+
+The easiest way to retrieve domain entities if using JPA is to leverage the capabilities of Spring Data.
+
+For example, simply by declaring this interface:
+
+[source,java]
+----
+public interface UserRepository extends Repository<User, Long> {
+
+  List<User> findByEmailAddressAndLastname(String emailAddress, String lastname);
+}
+----
+
+and Spring Data will create an implementation based on naming conventions.
+See the link:https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#reference[Spring Data] documentation for further details.
+
+It is also possible to declare JPQL queries , either on the repository method (using `javax.persistence.Query`) or on the entity (using `javax.persistence.NamedQuery`).
+
+On the entity, declare a named query, eg:
+
+[source,java]
+----
+@javax.persistence.Entity
+@javax.persistence.NamedQueries({
+    @javax.persistence.NamedQuery(          // <.>
+        name = "Customer.findByNameLike",   // <.>
+        query = "SELECT c " +               // <.>
+                "FROM Customer c " +        // <.>
+                "WHERE c.name LIKE :name"   // <.>
+    )
+    })
+...
+public class Customer {
+    // ...
+}
+----
+<.> There may be several `@NamedQuery` annotations, nested within a `@NamedQueries` annotation, defining queries using JPQL.
+<.> Defines the name of the query.
+<.> The definition of the query, using JPQL syntax.
+<.> The table name
+<.> The predicate, expressed using SQL syntax.
+
+and in the corresponding repository, use xref:refguide:applib:index/services/repository/RepositoryService.adoc[RepositoryService]:
+
+[source,java]
+----
+import org.springframework.stereotype.Repository;
+import lombok.RequiredArgsConstructor;
+
+@Repository
+@RequiredArgsConstructor(onConstructor_ = {@Inject} )
+public class CustomerRepository {
+
+    private final RepositoryService repositoryService;
+
+    public List<Customer> findByName(String name) {
+        return repositoryService.allMatches(                            // <.>
+                Query.named(Customer.class, "Customer.findByNameLike")  // <.>
+                     .withParameter("name", "%" + name + "%");          // <.>
+    }
+
+}
+----
+<.> The xref:refguide:applib:index/services/repository/RepositoryService.adoc[RepositoryService] is a generic facade over the ORM API.
+<.> Specifies the class that is annotated with @NamedQuery, along with the `@NamedQuery#name` attribute
+<.> The `:name` parameter in the query JPQL string, and its corresponding value
+
+
+[[finding-jdo]]
+=== Finding Objects (JDO)
+
 
-Retrieving domain entities depends on the ORM.
 In the case of xref:pjdo:ROOT:about.adoc[JDO/DataNucleus], it typically requires a JDOQL query defined on the domain entity, and a corresponding repository service for that domain entity type.
 This repository calls the framework-provided xref:refguide:applib:index/services/repository/RepositoryService.adoc[RepositoryService] to actually submit the query.
 
@@ -140,6 +216,7 @@ For example:
 
 [source,java]
 ----
+@javax.jdo.annotations.PersistenceCapable
 @javax.jdo.annotations.Queries({
     @javax.jdo.annotations.Query(                       // <.>
         name = "findByName",                            // <.>
@@ -152,14 +229,15 @@ public class Customer {
     // ...
 }
 ----
-<.> There may be several `@Query` annotations, nested within a `@Queries` annotation) defines queries using JDOQL.
+<.> There may be several `@Query` annotations, nested within a `@Queries` annotation, defining queries using JDOQL.
 <.> Defines the name of the query.
 <.> The definition of the query, using JDOQL syntax.
 <.> The fully-qualified class name.
 Must correspond to the class on which the annotation is defined (the framework checks this automatically on bootstrapping).
-<.> In this particular query, is an implementation of a LIKE "name%" query.
+<.> The predicate, expressed using Java syntax.
+In this particular query, is an implementation of a LIKE "name%" query.
 
-and
+and in the corresponding repository, use xref:refguide:applib:index/services/repository/RepositoryService.adoc[RepositoryService]:
 
 [source,java]
 ----
@@ -173,19 +251,16 @@ public class CustomerRepository {
     private final RepositoryService repositoryService;
 
     public List<Customer> findByName(String name) {
-        return repositoryService.allMatches(            // <.>
-                new QueryDefault<>(Customer.class,      // <.>
-                            "findByName",               // <.>
-                            "name",                     // <.>
-                            name);
+        return repositoryService.allMatches(                // <.>
+                Query.named(Customer.class, "findByName")   // <.>
+                     .withParameter("name", name);          // <.>
     }
 
 }
 ----
-<1>    The xref:refguide:applib:index/services/repository/RepositoryService.adoc[RepositoryService] is a generic facade over the JDO/DataNucleus API.
-<2> Specifies the class that is annotated with @Query
-<3> Corresponds to the `@Query#name` attribute
-<4> Corresponds to the `:name` parameter in the query JDOQL string
+<.> The xref:refguide:applib:index/services/repository/RepositoryService.adoc[RepositoryService] is a generic facade over the ORM API.
+<.> Specifies the class that is annotated with @Query, along with the `@Query#name` attribute
+<.> The `:name` parameter in the query JDOQL string, and its corresponding value
 
 Whenever a query is submitted, the framework will automatically "flush" any pending changes.
 This ensures that the database query runs against an up-to-date table so that all matching instances (with respect to the current transaction) are correctly retrieved.
@@ -194,9 +269,9 @@ When an object is loaded from the database the framework will emit `ObjectLoaded
 
 === Type-safe queries
 
-DataNucleus also supports type-safe queries; these can be executed using the xref:pjdo:ROOT:services/IsisJdoSupport.adoc[IsisJdoSupport] (JDO-specific) domain service.
+DataNucleus also supports type-safe queries; these can be executed using the xref:refguide:persistence:index/jdo/applib/services/JdoSupportService.adoc[JdoSupportService] (JDO-specific) domain service.
 
-See xref:pjdo:ROOT:services/IsisJdoSupport.adoc#type-safe-jdoql-queries[here] for further details.
+See xref:refguide:persistence:index/jdo/applib/services/JdoSupportService.adoc#type-safe-jdoql-queries[JdoSupportService] for further details.
 
 [[updating]]
 == Updating Objects
@@ -208,7 +283,7 @@ That said, it is possible to "flush" pending changes:
 
 * xref:refguide:applib:index/services/xactn/TransactionService.adoc[TransactionService] acts at the Apache Isis layer, and flushes any pending object persistence or object deletions
 
-* (if using xref:pjdo:ROOT:about.adoc[JDO/DataNucleus]), the xref:pjdo:ROOT:services/IsisJdoSupport.adoc[IsisJdoSupport] domain service can be used reach down to the underlying JDO API, and perform a flush of pending object updates also.
+* (if using xref:pjdo:ROOT:about.adoc[JDO/DataNucleus]), the xref:refguide:persistence:index/jdo/applib/services/JdoSupportService.adoc[JdoSupportService] domain service can be used reach down to the underlying JDO API, and perform a flush of pending object updates also.
 
 When an object is updated the framework will emit `ObjectUpdatingEvent` and `ObjectUpdatedEvent` xref:userguide:fun:overview.adoc#lifecycle-events[lifecycle events].
 
diff --git a/antora/components/userguide/modules/fun/pages/domain-entities-and-services/domain-entities.adoc b/antora/components/userguide/modules/fun/pages/domain-entities-and-services/domain-entities.adoc
index bb1bda5..91cf1f6 100644
--- a/antora/components/userguide/modules/fun/pages/domain-entities-and-services/domain-entities.adoc
+++ b/antora/components/userguide/modules/fun/pages/domain-entities-and-services/domain-entities.adoc
@@ -10,7 +10,9 @@ As such, they are mapped to a persistent object store, typically an RDBMS, with
 
 The vast majority of such entities will be annotated with `@DomainObject(nature=ENTITY)`.
 In addition they will also require ORM metadata.
-For example, xref:pjdo:ROOT:about.adoc[JDO/DataNucleus] requires the
+
+* for xref:pjpa:ROOT:about.adoc[JPA/Eclipselink], this requires the `javax.persistence.Entity` annotation
+* for xref:pjdo:ROOT:about.adoc[JDO/DataNucleus], this requires the
 `@javax.jdo.annotations.PersistenceCapable` annotation.
 
 TIP: It is also possible to specify ORM metadata using `.xml` files.
@@ -18,10 +20,29 @@ TIP: It is also possible to specify ORM metadata using `.xml` files.
 In this section we discuss the mechanics of writing domain objects that comply with Apache Isis' programming model.
 
 
+== Entities (JPA)
+
+JPA entities are typically defined using the `@javax.persistence.Entity` annotation, with additional annotations to define their primary key, scalar properties and relationships to other entities.
+
+For some examples, see:
+
+* https://www.baeldung.com/jpa-entities[Baeldung article on JPA entities]
+* https://spring.io/guides/gs/accessing-data-jpa/[Spring Data tutorial]
+* https://docs.jboss.org/hibernate/annotations/3.5/reference/en/html/entity.html#entity-mapping[JBoss tutorial]
+
+See the xref:pjpa:ROOT:about.adoc[JPA/Eclipselink object store] for further information on annotating domain entities.
+
+
+== Entities (JDO)
+
+See the xref:pjdo:ROOT:about.adoc[JDO/DataNucleus object store] for further information on annotating domain entities.
+
 [#persistencecapable]
-== PersistenceCapable (JDO)
+=== PersistenceCapable
+
+NOTE: This section is specific to the xref:pjdo:ROOT:about.adoc[JDO API]
 
-Focusing for now on the xref:pjdo:ROOT:about.adoc[JDO API], entities are flagged as being "persistence capable", indicating how the ORM should manage their identity:
+With the xref:pjdo:ROOT:about.adoc[JDO API], entities are flagged as being "persistence capable", indicating how the ORM should manage their identity:
 
 [source,java]
 ----
@@ -64,7 +85,9 @@ It isn't necessary to include this annotation -- at least, not for entities -- b
 In particular, its strongly recommended that the `objectType` (which acts like an alias to the concrete domain class) is specified; note that it corresponds to the schema/table for DataNucleus' `@PersistenceCapable` annotation.
 
 
-== Key Properties
+=== Key Properties (JDO)
+
+NOTE: This section is specific to the xref:pjdo:ROOT:about.adoc[JDO API]
 
 All domain entities will have some sort of mandatory key properties.
 The example below is a very simple case, where the entity is identified by a `name` property.
@@ -124,7 +147,9 @@ The ORM will create a no-arg constructor to allow domain entity to be rehydrated
 <.> Use `java.util.Comparator#comparing()` to implement `Comparable` interface.
 
 
-== Queries
+=== Queries (JDO)
+
+NOTE: This section is specific to the xref:pjdo:ROOT:about.adoc[JDO API]
 
 When using JDO, it's also common for domain entities to have queries annotated on them.
 These are used by repository domain services to query for instances of the entity:
@@ -163,10 +188,10 @@ The corresponding repository method for the above query is:
 [source,java]
 ----
 public List<SimpleObject> findByName(String name) {
-    return repositoryService.allMatches(              // <.>
-            new QueryDefault<>(SimpleObject.class,    // <.>
-                        "findByName",                 // <.>
-                        "name", name                  // <.>
+    return repositoryService.allMatches(            // <.>
+            Query.named(SimpleObject.class,         // <.>
+                        "findByName")               // <.>
+                   .withParameter("name", name)     // <.>
             );
 }
 
@@ -179,8 +204,4 @@ RepositoryService repositoryService;
 <.> Corresponds to the `:name` parameter in the query JDOQL string
 
 
-[TIP]
-====
-See the xref:pjdo:ROOT:about.adoc[JDO/DataNucleus object store] for further information on annotating domain entities.
-====
 
diff --git a/antora/components/userguide/modules/fun/pages/domain-entities-and-services/domain-services.adoc b/antora/components/userguide/modules/fun/pages/domain-entities-and-services/domain-services.adoc
index 36d9a6c..179e470 100644
--- a/antora/components/userguide/modules/fun/pages/domain-entities-and-services/domain-services.adoc
+++ b/antora/components/userguide/modules/fun/pages/domain-entities-and-services/domain-services.adoc
@@ -62,8 +62,8 @@ public CustomerRepository {
 
     public List<Customer> findByName(String name) {
         return repositoryService.allMatches(                    // <.>
-                new QueryDefault<>(Customer.class,
-                            "findByName", "name", name);
+                Query.named(Customer.class, "findByName")
+                    .withParameter("name", name));
     }
 
     public Customer newCustomer(...) {
@@ -90,7 +90,7 @@ There is no need to annotate the actions; they are implicitly hidden because of
 [TIP]
 ====
 JDO/Datanucleus also supports link:http://www.datanucleus.org:15080/products/accessplatform_5_2/jdo/query.html#jdoql_typed[type-safe queries].
-These can be executed through the xref:pjdo:ROOT:services/IsisJdoSupport.adoc[IsisJdoSupport] domain service.
+These can be executed through the xref:refguide:persistence:index/jdo/applib/services/JdoSupportService.adoc[JdoSupportService] domain service.
 ====
 
 == Menu
diff --git a/core/interaction/src/main/java/org/apache/isis/core/interaction/session/MessageBroker.java b/core/interaction/src/main/java/org/apache/isis/core/interaction/session/MessageBroker.java
index 23a0117..f67869e 100644
--- a/core/interaction/src/main/java/org/apache/isis/core/interaction/session/MessageBroker.java
+++ b/core/interaction/src/main/java/org/apache/isis/core/interaction/session/MessageBroker.java
@@ -57,6 +57,10 @@ public class MessageBroker implements Serializable {
     }
 
     public void addMessage(final String message) {
+        if(messages.contains(message)) {
+            // just ignore it...
+            return;
+        }
         messages.add(message);
     }
 
diff --git a/persistence/jdo/adoc/modules/ROOT/pages/hints-and-tips/typesafe-queries-and-fetchgroups.adoc b/persistence/jdo/adoc/modules/ROOT/pages/hints-and-tips/typesafe-queries-and-fetchgroups.adoc
index 72b6db9..2a9c801 100644
--- a/persistence/jdo/adoc/modules/ROOT/pages/hints-and-tips/typesafe-queries-and-fetchgroups.adoc
+++ b/persistence/jdo/adoc/modules/ROOT/pages/hints-and-tips/typesafe-queries-and-fetchgroups.adoc
@@ -34,10 +34,9 @@ which normally would be used from a repository:
 public List<IncomingInvoice> findCompletedOrLaterWithItemsByReportedDate(
         final LocalDate reportedDate) {
     return repositoryService.allMatches(
-            new QueryDefault<>(
-                    IncomingInvoice.class,
-                    "findCompletedOrLaterWithItemsByReportedDate",
-                    "reportedDate", reportedDate));
+            Query.named(IncomingInvoice.class,
+                    "findCompletedOrLaterWithItemsByReportedDate")
+               .withParameter("reportedDate", reportedDate));
 }
 ----