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/10/03 21:28:48 UTC
[isis] branch ISIS-2873-petclinic updated: ISIS-2873: ex 4.2
through 4.9
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch ISIS-2873-petclinic
in repository https://gitbox.apache.org/repos/asf/isis.git
The following commit(s) were added to refs/heads/ISIS-2873-petclinic by this push:
new 72100bd ISIS-2873: ex 4.2 through 4.9
72100bd is described below
commit 72100bd6c0bbe7f0a75ccfc7a1a25daf9343335a
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sun Oct 3 22:28:34 2021 +0100
ISIS-2873: ex 4.2 through 4.9
---
.../petclinic/images/04-07/download-layout-xml.png | Bin 0 -> 17163 bytes
.../modules/petclinic/pages/040-pet-entity.adoc | 605 +++++++++++++++++----
.../jpa/eclipselink/config/ElSettings.java | 2 +-
3 files changed, 502 insertions(+), 105 deletions(-)
diff --git a/antora/components/tutorials/modules/petclinic/images/04-07/download-layout-xml.png b/antora/components/tutorials/modules/petclinic/images/04-07/download-layout-xml.png
new file mode 100644
index 0000000..dc052b3
Binary files /dev/null and b/antora/components/tutorials/modules/petclinic/images/04-07/download-layout-xml.png differ
diff --git a/antora/components/tutorials/modules/petclinic/pages/040-pet-entity.adoc b/antora/components/tutorials/modules/petclinic/pages/040-pet-entity.adoc
index b6ff17c..e00ced4 100644
--- a/antora/components/tutorials/modules/petclinic/pages/040-pet-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/040-pet-entity.adoc
@@ -47,6 +47,7 @@ public @interface PetName {
* create the `Pet` entity, using the `@PetName` meta-annotation for the `name` property:
+
[source,java]
+.Pet.java
----
@Entity
@Table(
@@ -83,7 +84,8 @@ public class Pet implements Comparable<Pet> {
}
- @JoinColumn(name = "owner_id", nullable = false)
+ @ManyToOne(optional = false)
+ @JoinColumn(name = "owner_id")
@PropertyLayout(fieldSetId = "name", sequence = "1")
@Getter @Setter
private PetOwner petOwner;
@@ -110,10 +112,44 @@ Run the application, and confirm that the table is created correctly using menu:
-== Exercise 4.2: Add PetOwner's collection of Pets
+== Exercise 4.2: Add PetRepository
-At this point in our app, although the `Pet` knows its `PetOwner`, the opposite isn't true.
-In this exercise we'll add that collection:
+We will need to find the ``Pet``s belonging to a `PetOwner`.
+We do this by introducing a `PetRepository`, implemented as a link:https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.definition[Spring Data repository].
+
+=== Solution
+
+[source,bash]
+----
+git checkout tags/04-02-PetRepository
+mvn clean install
+mvn -pl spring-boot:run
+----
+
+
+
+=== Tasks
+
+* create the `PetRepository`, extending Spring Data's `org.springframework.data.repository.Repository` interface:
++
+[source,java]
+.PetRepository.java
+----
+import org.springframework.data.repository.Repository;
+
+public interface PetRepository extends Repository<Pet, Long> {
+
+ List<Pet> findByPetOwner(PetOwner petOwner);
+}
+----
+
+Confirm the application still runs
+
+
+
+== Exercise 4.3: Add PetOwner's collection of Pets
+
+In this next exercise we'll add the ``PetOwner``'s collection of ``Pet``s, using a xref:userguide:fun:mixins.adoc[mixin].
[plantuml]
----
@@ -149,30 +185,44 @@ PetOwner *-r--> "0..*" Pet
[source,bash]
----
-git checkout tags/04-02-PetOwner-pets-collection
+git checkout tags/04-03-PetOwner-pets-mixin-collection
mvn clean install
mvn -pl spring-boot:run
----
=== Tasks
-* in the `PetOwner` class, add the `pets` collection:
+* create the `PetOwner_pets` mixin class:
+
[source,java]
----
-@OneToMany(
- mappedBy = "petOwner", // <.>
- cascade = CascadeType.ALL // <.>
-)
-@Getter
+import org.apache.isis.applib.annotation.Collection;
+import org.apache.isis.applib.annotation.CollectionLayout;
+
+import lombok.RequiredArgsConstructor;
+
+@Collection // <.>
@CollectionLayout(defaultView = "table")
-private Set<Pet> pets = new TreeSet<>();
+@RequiredArgsConstructor // <.>
+public class PetOwner_pets { // <.>
+
+ private final PetOwner petOwner; // <.>
+
+ public List<Pet> coll() {
+ return petRepository.findByPetOwner(petOwner); // <.>
+ }
+
+ @Inject PetRepository petRepository; // <5>
+}
----
-<.> specifies a bidirectional property.
-(`Pet#petOwner` "points back to" the `PetOwner`).
-<.> Deleting an `PetOwner` will also delete any associated ``Pet``s.
-+
-Run the application to check the mapping is correct
+<.> indicates that this is a collection mixin
+<.> lombok annotation to avoid some boilerplate
+<.> collection name is derived from the mixin class name, being the name after the '_'.
+<.> the "mixee" that is being contributed to, in other words `PetOwner`.
+<.> inject the `PetRepository` as defined in previous exercise, in order to find the ``Pet``s owned by the `PetOwner`.
+
+* Run the application to confirm that the `pets` collection is visible (it won't have any `Pet` instances in it just yet).
+
* update the `PetOwner.layout.xml` file to specify the position of the `pets` collection.
For example:
@@ -204,7 +254,7 @@ For example:
----
<.> define a tab on the right hand side to hold the `pets` collection.
+
-Run the application to confirm that the `pets` collection is visible (it won't have any `Pet` instances in it just yet).
+Run the application (or just reload the changed classes) and confirm the positioning the `pets` collection.
* Create a column order file to define the order of columns in the ``PetOwner``'s `pets` collection:
@@ -216,12 +266,14 @@ name
id
----
+
-Reload the changed classes and confirm the columns of the `pets` collection are correct.
+Run the application (or just reload the changed classes) and confirm the columns of the `pets` collection are correct.
+
+== Exercise 4.4: Add Pet's remaining properties
-== Exercise 4.3: Add Pet's remaining properties
+In this exercise we'll add the remaining properties for `Pet`.
[plantuml]
----
@@ -258,7 +310,7 @@ Pet "*" -u-> PetSpecies
[source,bash]
----
-git checkout tags/04-03-pet-remaining-properties
+git checkout tags/04-04-pet-remaining-properties
mvn clean install
mvn -pl spring-boot:run
----
@@ -266,11 +318,10 @@ mvn -pl spring-boot:run
=== Tasks
-* TODO
-
* declare the `PetSpecies` enum:
+
[source,java]
+.PetSpecies.java
----
public enum PetSpecies {
Dog,
@@ -280,94 +331,180 @@ public enum PetSpecies {
}
----
-
* add in a reference to `PetSpecies`:
+
[source,java]
+.Pet.java
----
-@javax.jdo.annotations.Column(allowsNull = "false")
-@Property(editing = Editing.DISABLED)
+@Enumerated(EnumType.STRING) // <.>
+@Column(nullable = false)
@Getter @Setter
+@PropertyLayout(fieldSetId = "details", sequence = "1") // <.>
private PetSpecies petSpecies;
----
+<.> mapped to a string rather than an integer value in the database
+<.> anticipates adding a 'details' fieldSet in the layout xml (see xref:#exercise-4-7-add-pets-ui-customisation[ex 4.7])
-* As this is mandatory, we also need to update the constructor:
+* As the `petSpecies` property is mandatory, also update the constructor:
+
[source,java]
+.Pet.java
----
-// ...
-public Pet(final PetOwner petOwner, final String name, final PetSpecies petSpecies) {
+Pet(PetOwner petOwner, String name, PetSpecies petSpecies) {
this.petOwner = petOwner;
this.name = name;
this.petSpecies = petSpecies;
}
----
-* finally, let's add in `notes` optional property:
+* add in an optional `notes` property:
+
[source,java]
----
-@javax.jdo.annotations.Column(allowsNull = "true", length = 4000)
-@Property(editing = Editing.ENABLED)
+@Notes
+@Column(length = Notes.MAX_LEN, nullable = true)
@Getter @Setter
+@Property(commandPublishing = Publishing.ENABLED, executionPublishing = Publishing.ENABLED)
+@PropertyLayout(fieldSetId = "notes", sequence = "1")
private String notes;
----
+Run the application and use menu:Prototyping[H2 Console] to confirm the database schema for `Pet` is as expected.
-== Exercise 4.4: Add PetOwner action to add Pets
+== Exercise 4.5: Digression: clean-up casing of database schema
+
+Reviewing the tables in the database we can see that we have a mix between lower- and upper-case table and column names.
+In this exercise we'll take a timeout to make everything consistent.
+
+=== Solution
-We'll make the addition (and removal) of ``Pet``s a responsibility of `PetOwner`.
+[source,bash]
+----
+git checkout tags/04-05-db-schema-consistent-casings
+mvn clean install
+mvn -pl spring-boot:run
+----
+=== Tasks
+
+* check out the tag and inspect the changes:
+
+** `Pet` entity table name
+** `PetOwner` entity table name and column names
+** JDBC URL
+
+* run the application to check the database schema.
+
+
+
+== Exercise 4.6: Add PetOwner action to add Pets
+
+In this exercise we'll bring in the capability to add ``Pet``s, as a responsibility of `PetOwner`.
+We'll use an mixin action to implement this.
=== Solution
[source,bash]
----
-git checkout tags/04-04-PetOOwner-addPet-action
+git checkout tags/04-06-PetOwner-addPet-action
mvn clean install
mvn -pl spring-boot:run
----
=== Tasks
-* TODO
+* create the `PetOwner_addPet` action mixin:
++
+[source,java]
+.PetOwner_addPet.java
+----
+@Action( // <.>
+ semantics = SemanticsOf.IDEMPOTENT,
+ commandPublishing = Publishing.ENABLED,
+ executionPublishing = Publishing.ENABLED
+)
+@ActionLayout(associateWith = "pets") // <.>
+@RequiredArgsConstructor
+public class PetOwner_addPet { // <.>
+
+ private final PetOwner petOwner; // <.>
+
+ public PetOwner act(
+ @PetName final String name,
+ final PetSpecies petSpecies
+ ) {
+ repositoryService.persist(new Pet(petOwner, name, petSpecies));
+ return petOwner;
+ }
+
+ @Inject RepositoryService repositoryService;
+}
+----
+<.> indicates that this class is a mixin action.
+<.> the action is associated with the "pets" collection (defined earlier).
+This means that in the UI, the button representing the action will be rendered close to the table representing the "pets" collection.
+<.> the action name "addPet" is derived from the mixin class name.
++
+Run the application and verify that ``Pet``s can now be added to ``PetOwner``s.
+
+Let's now add some validation to ensure that two pets with the same name cannot be added.
+
+* first, we need a new method in `PetRepository`:
++
+[source,java]
+.PetRepository.java
+----
+Optional<Pet> findByPetOwnerAndName(PetOwner petOwner, String name);
+----
-* add an `addPet` action to `PetOwner`:
+* Now use a supporting xref:userguide:fun:business-rules/validity.adoc[validate] method to prevent two pets with the same name from being added:
+
[source,java]
+.PetOwner_addPet.java
----
-@Action(semantics = SemanticsOf.NON_IDEMPOTENT)
-public Pet addPet(final String name, final PetSpecies petSpecies) {
- return repositoryService.persist(new Pet(this, name, petSpecies));
+public String validate0Act(final String name) {
+ return petRepository.findByPetOwnerAndName(petOwner, name).isPresent()
+ ? String.format("Pet with name '%s' already defined for this owner", name)
+ : null;
}
+
+@Inject PetRepository petRepository;
----
++
+NOTE: we could also just rely on the database, but adding a check here will make for better UX.
++
+Run the application and check the validation message is fired when you attempt to add two ``Pet``s with the same name for the same `PetOwner` (but two different ``PetOwner``s should be able to have a ``Pet`` with the same name).
-* update the `addPet` action to associate with the `pets` collection:
+* Let's suppose that owners own dogs for this particular clinic.
+Use a xref:refguide:applib-methods:prefixes.adoc#default[default] supporting method to default the petSpecies parameter:
+
[source,java]
+.PetOwner_addPet.java
----
-@Action(
- semantics = SemanticsOf.NON_IDEMPOTENT,
- associateWith = "pets"
-)
-public Pet newPet(final String name, final PetSpecies petSpecies) { ... }
+public PetSpecies default1Act() {
+ return PetSpecies.Dog;
+}
----
++
+Run the application once more to test.
-== Exercise 4.5: Add Pet's UI files
+[#exercise-4-7-add-pets-ui-customisation]
+== Exercise 4.7: Add Pet's UI customisation
+If we run the application and create a `Pet`, then the framework will render a page but the layout could be improved.
+So in this exercise we'll add a layout file for `Pet` and other UI files.
-TODO
=== Solution
[source,bash]
----
-git checkout tags/04-05-Pet-ui-files
+git checkout tags/04-07-Pet-ui-customisation
mvn clean install
mvn -pl spring-boot:run
----
@@ -375,27 +512,148 @@ mvn -pl spring-boot:run
=== Tasks
-* TODO
+* Create a `Pet.layout.xml` file as follows:
++
+[source,xml]
+.Pet.layout.xml
+----
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<bs3:grid xsi:schemaLocation="http://isis.apache.org/applib/layout/component http://isis.apache.org/applib/layout/component/component.xsd http://isis.apache.org/applib/layout/links http://isis.apache.org/applib/layout/links/links.xsd http://isis.apache.org/applib/layout/grid/bootstrap3 http://isis.apache.org/applib/layout/grid/bootstrap3/bootstrap3.xsd" xmlns:bs3="http://isis.apache.org/applib/layout/grid/bootstrap3" xmlns:cpt="http://isis.apache.org/applib/layout/component" xmlns:lnk="h [...]
+ <bs3:row>
+ <bs3:col span="12" unreferencedActions="true">
+ <cpt:domainObject bookmarking="AS_ROOT"/>
+ </bs3:col>
+ </bs3:row>
+ <bs3:row>
+ <bs3:col span="6">
+ <bs3:row>
+ <bs3:col span="12">
+ <bs3:tabGroup>
+ <bs3:tab name="General">
+ <bs3:row>
+ <bs3:col span="12">
+ <cpt:fieldSet id="name"/>
+ </bs3:col>
+ </bs3:row>
+ </bs3:tab>
+ <bs3:tab name="Metadata">
+ <bs3:row>
+ <bs3:col span="12">
+ <cpt:fieldSet name="Metadata" id="metadata"/>
+ </bs3:col>
+ </bs3:row>
+ </bs3:tab>
+ <bs3:tab name="Other">
+ <bs3:row>
+ <bs3:col span="12">
+ <cpt:fieldSet name="Other" id="other" unreferencedProperties="true"/>
+ </bs3:col>
+ </bs3:row>
+ </bs3:tab>
+ </bs3:tabGroup>
+ <cpt:fieldSet id="details" name="Details"/>
+ <cpt:fieldSet id="notes" name="Notes"/>
+ </bs3:col>
+ </bs3:row>
+ <bs3:row>
+ <bs3:col span="12">
+ </bs3:col>
+ </bs3:row>
+ </bs3:col>
+ <bs3:col span="6">
+ <bs3:tabGroup unreferencedCollections="true"/>
+ </bs3:col>
+ </bs3:row>
+</bs3:grid>
+----
+
+* reload changed classes (or run the application), and check the layout.
++
+TIP: if the layout isn't quite as you expect, try using menu:Metadata[Rebuild metamodel] to force the domain object metamodel to be recreated.
+
+* add a `Pet.png` file to act as the icon, in the same package.
++
+This might be a good point to find a better icon for `PetOwner`, too.
+
+* we also need a title for each `Pet`, which we can provide using a
+xref:refguide:applib-methods:ui-hints.adoc#title[title()] method:
++
+[source,java]
+.Pet.java
+----
+public String title() {
+ return getName() + " " + getPetOwner().getLastName();
+}
+----
+
+In the same way that titles are specific an object instance, we can also customise the icon:
+
+* download additional icons for each of the `PetSpecies` (dog, cat, hamster, budgie)
-* Add a `Pet.layout.xml` file, in the same package as `Pet`.
+* save these icons as `Pet-dog.png`, `Pet-cat.png` and so on, ie the pet species as suffix.
+
+* implement the xref:refguide:applib-methods:ui-hints.adoc#iconName[iconName()] method as follows:
++
+[source,java]
+.Pet.java
+----
+public String iconName() {
+ return getPetSpecies().name().toLowerCase();
+}
+----
+
+* Run the application.
+You should find that the appropriate icon is selected based upon the species of the `Pet`.
+
+
+* One further tweak is to show both the title and icon for objects in tables.
+This can be done by changing some configuration properties:
++
+[source,yaml]
+.application-custom.yml
+----
+isis:
+ viewer:
+ wicket:
+ max-title-length-in-standalone-tables: 15
+ max-title-length-in-parented-tables: 15
+----
++
+also update the `application.css` file, otherwise the icon and title will be centred:
++
+[source,css]
+.application.css
+----
+td.title-column > div > div > div {
+ text-align: left;
+}
+.collectionContentsAsAjaxTablePanel table.contents thead th.title-column,
+.collectionContentsAsAjaxTablePanel table.contents tbody td.title-column {
+ width: 10%;
+}
+----
-* Add a `Pet.png`, in the same package as `Pet`.
+=== Optional exercise
+An alternative way to create the layout file is to run the application, obtain/create an instance of the domain object in question (eg `Pet`) and then download the inferred layout XML from the metadata menu:
+image::04-07/download-layout-xml.png[width=400]
-== Exercise 4.6: Update fixture script to create Pets with PetOwners
-Before we go any further, let's take some time out to extend our fixture so that each `PetOwner` also has some ``Pet``s.
-TODO
+== Exercise 4.8: Update fixture script using Pet personas
+
+By now you are probably tiring of continually creating a Pet in order to perform your tests.
+So let's take some time out to extend our fixture so that each `PetOwner` also has some ``Pet``s.
+
=== Solution
[source,bash]
----
-git checkout tags/04-06-Pet-fixture-script
+git checkout tags/04-08-Pet-personas
mvn clean install
mvn -pl spring-boot:run
----
@@ -403,104 +661,243 @@ mvn -pl spring-boot:run
=== Tasks
-* TODO
+* First we need to modify the `PetOwnerBuilder` to make it idempotent:
++
+[source,java]
+.PetOwnerBuilder.java
+----
+@Accessors(chain = true)
+public class PetOwnerBuilder extends BuilderScriptWithResult<PetOwner> {
+
+ @Getter @Setter
+ private String name;
+
+ @Override
+ protected PetOwner buildResult(final ExecutionContext ec) {
+
+ checkParam("name", ec, String.class);
+ PetOwner petOwner = petOwners.findByLastNameExact(name);
+ if(petOwner == null) {
+ petOwner = wrap(petOwners).create(name, null);
+ }
+ return this.object = petOwner;
+ }
-* update `RecreateOwners` by adding a `PetData` (Lombok) data class:
+ @Inject PetOwners petOwners;
+}
+----
+* Now we create a similar `PetBuilder` fixture script to add ``Pet``s through a `PetOwner`:
+
[source,java]
+.PetBuilder.java
----
-@Data
-static class PetData {
- private final String name;
- private final PetSpecies petSpecies;
+@Accessors(chain = true)
+public class PetBuilder extends BuilderScriptWithResult<Pet> {
+
+ @Getter @Setter String name;
+ @Getter @Setter PetSpecies petSpecies;
+ @Getter @Setter PetOwner_persona petOwner_persona;
+
+ @Override
+ protected Pet buildResult(final ExecutionContext ec) {
+
+ checkParam("name", ec, String.class);
+ checkParam("petSpecies", ec, PetSpecies.class);
+ checkParam("petOwner_persona", ec, PetOwner_persona.class);
+
+ PetOwner petOwner = ec.executeChildT(this, petOwner_persona.builder()).getObject(); // <.>
+
+ Pet pet = petRepository.findByPetOwnerAndName(petOwner, name).orElse(null);
+ if(pet == null) {
+ wrapMixin(PetOwner_addPet.class, petOwner).act(name, petSpecies); // <.>
+ pet = petRepository.findByPetOwnerAndName(petOwner, name).orElseThrow();
+ }
+
+ return this.object = pet;
+ }
+
+ @Inject PetRepository petRepository;
}
----
+<.> Transitively sets up its prereqs (`PetOwner`).
+This relies on thefact that `PetOwnerBuilder` is idempotent.
+<.> calls domain logic to add a `Pet` if required
-* factor out a `createOwner` helper method:
+* Now we create a "persona" enum for ``Pet``s:
+
[source,java]
+.Pet_persona.java
----
-private Owner createOwner(
- final String lastName,
- final String firstName,
- final String phoneNumber,
- final PetData... pets) {
- Owner owner = this.owners.create(lastName, firstName, phoneNumber);
- for (PetData pet : pets) {
- owner.newPet(pet.name, pet.petSpecies);
+@AllArgsConstructor
+public enum Pet_persona
+implements PersonaWithBuilderScript<PetBuilder>, PersonaWithFinder<Pet> {
+
+ TIDDLES_JONES("Tiddles", PetSpecies.Cat, PetOwner_persona.JONES),
+ ROVER_JONES("Rover", PetSpecies.Dog, PetOwner_persona.JONES),
+ HARRY_JONES("Harry", PetSpecies.Hamster, PetOwner_persona.JONES),
+ BURT_JONES("Burt", PetSpecies.Budgerigar, PetOwner_persona.JONES),
+ TIDDLES_FARRELL("Tiddles", PetSpecies.Cat, PetOwner_persona.FARRELL),
+ SPIKE_FORD("Spike", PetSpecies.Dog, PetOwner_persona.FORD),
+ BARRY_ITOJE("Barry", PetSpecies.Budgerigar, PetOwner_persona.ITOJE);
+
+ @Getter private final String name;
+ @Getter private final PetSpecies petSpecies;
+ @Getter private final PetOwner_persona petOwner_persona;
+
+ @Override
+ public PetBuilder builder() {
+ return new PetBuilder() // <.>
+ .setName(name) // <.>
+ .setPetSpecies(petSpecies)
+ .setPetOwner_persona(petOwner_persona);
+ }
+
+ @Override
+ public Pet findUsing(final ServiceRegistry serviceRegistry) { // <.>
+ PetOwner petOwner = petOwner_persona.findUsing(serviceRegistry);
+ PetRepository petRepository = serviceRegistry.lookupService(PetRepository.class).orElseThrow();
+ return petRepository.findByPetOwnerAndName(petOwner, name).orElse(null);
+ }
+
+ public static class PersistAll
+ extends PersonaEnumPersistAll<Pet_persona, Pet> {
+ public PersistAll() {
+ super(Pet_persona.class);
+ }
}
- return owner;
}
----
+<.> Returns the `PetBuilder` added earlier
+<.> Copies over the state of the enum to the builder
+<.> Personas can also be used to lookup domain entities.
+The xref:refguide:applib:index/services/registry/ServiceRegistry.adoc[ServiceRegistry] can be used as a service locator of any domain service (usually a repository).
-* and update `execute` to use both:
+* Finally, update the top-level `PetClinicDemo` to create both ``Pet``s and also ``PetOwner``s.
+
[source,java]
+.PetClinicDemo.java
----
-@Override
-protected void execute(final ExecutionContext ec) {
-
- isisJdoSupport.deleteAll(Pet.class);
- isisJdoSupport.deleteAll(Owner.class);
-
- ec.addResult(this,
- createOwner("Smith", "John", null,
- new PetData("Rover", PetSpecies.Dog))
- );
- ec.addResult(this,
- createOwner("Jones", "Mary", "+353 1 555 1234",
- new PetData("Tiddles", PetSpecies.Cat),
- new PetData("Harry", PetSpecies.Budgerigar)
- ));
- ec.addResult(this,
- createOwner("Hughes", "Fred", "07777 987654",
- new PetData("Jemima", PetSpecies.Hamster)
- ));
+public class PetClinicDemo extends FixtureScript {
+
+ @Override
+ protected void execute(final ExecutionContext ec) {
+ ec.executeChildren(this, moduleWithFixturesService.getTeardownFixture());
+ ec.executeChild(this, new Pet_persona.PersistAll());
+ ec.executeChild(this, new PetOwner_persona.PersistAll());
+ }
+
+ @Inject ModuleWithFixturesService moduleWithFixturesService;
}
----
-* rename from `RecreateOwners` to `RecreateOwnersAndPets`
-== Exercise 4.7: Add PetOwner action to delete a Pet
-we will probably also need to delete an action to delete a `Pet` (though once there are associated ``Visit``s for a `Pet`, we'll need to disable this action).
+== Exercise 4.9: Add PetOwner action to delete a Pet
+
+We will probably also need to delete an action to delete a `Pet` (though once there are associated ``Visit``s for a `Pet`, we'll need to disable this action).
-TODO
=== Solution
[source,bash]
----
-git checkout tags/04-06-PetOwner-deletePet-action
+git checkout tags/04-09-PetOwner-deletePet-action
mvn clean install
mvn -pl spring-boot:run
----
+=== Tasks
++ create a new action mixins, `PetOwner_removePet`:
+
[source,java]
+.PetOwner_removePet.java
----
@Action(
- semantics = SemanticsOf.NON_IDEMPOTENT,
- associateWith = "pets", associateWithSequence = "2"
+ semantics = SemanticsOf.IDEMPOTENT,
+ commandPublishing = Publishing.ENABLED,
+ executionPublishing = Publishing.ENABLED
)
-public Owner removePet(Pet pet) {
- repositoryService.removeAndFlush(pet);
- return this;
+@ActionLayout(associateWith = "pets", sequence = "2")
+@RequiredArgsConstructor
+public class PetOwner_removePet {
+
+ private final PetOwner petOwner;
+
+ public PetOwner act(@PetName final String name) {
+ petRepository.findByPetOwnerAndName(petOwner, name)
+ .ifPresent(pet -> repositoryService.remove(pet));
+ return petOwner;
+ }
+
+ @Inject PetRepository petRepository;
+ @Inject RepositoryService repositoryService;
+}
+----
+
+* To be explicit, add in an xref:refguide:applib:index/annotation/ActionLayout.adoc#sequence[@ActionLayout#sequence] for "addPet" also:
++
+[source,java]
+.PetOwner_addPet.java
+----
+// ...
+@ActionLayout(associateWith = "pets", sequence = "1")
+// ...
+public class PetOwner_addPet {
+ // ...
+}
+----
+
+* Run the application and test the action; it should work, but requires the ``Pet``'s `name` to be spelt exactly correctly.
+
+* Use a xref:refguide:applib-methods:prefixes.adoc#choices[choices] supporting method to restrict the list of `Pet` ``name``s:
++
+[source,java]
+.PetOwner_removePet.java
+----
+public List<String> choices0Act() {
+ return petRepository.findByPetOwner(petOwner)
+ .stream()
+ .map(Pet::getName)
+ .collect(Collectors.toList());
+}
+----
+
+* We also should xref:refguide:applib-methods:prefixes.adoc#disable[disable] (grey out) the `removePet` action if the `PetOwner` has no ``Pet``s:
++
+[source,java]
+.PetOwner_removePet.java
+----
+public String disableAct() {
+ return petRepository.findByPetOwner(petOwner).isEmpty() ? "No pets" : null;
+}
+----
+
+* As a final refinement, if there is exactly one `Pet` then that could be the xref:refguide:applib-methods:prefixes.adoc#default[default]:
++
+[source,java]
+.PetOwner_removePet.java
+----
+public String default0Act() {
+ List<String> names = choices0Act();
+ return names.size() == 1 ? names.get(0) : null;
}
----
-When the `removePet` action is invoked, note how the available ``Pet``s is restricted to those in the collection.
-This is due to the `@Action#associateWith` attribute.
+
+=== Optional exercise
+
+TODO: use @Action(choicesFrom="pets")
+
diff --git a/persistence/jpa/eclipselink/src/main/java/org/apache/isis/persistence/jpa/eclipselink/config/ElSettings.java b/persistence/jpa/eclipselink/src/main/java/org/apache/isis/persistence/jpa/eclipselink/config/ElSettings.java
index 9211fb9..e28c9c6 100644
--- a/persistence/jpa/eclipselink/src/main/java/org/apache/isis/persistence/jpa/eclipselink/config/ElSettings.java
+++ b/persistence/jpa/eclipselink/src/main/java/org/apache/isis/persistence/jpa/eclipselink/config/ElSettings.java
@@ -71,7 +71,7 @@ public class ElSettings {
val jpaProps = new HashMap<String, Object>();
// setup defaults
- jpaProps.put(PersistenceUnitProperties.WEAVING, "false");
+ jpaProps.put(PersistenceUnitProperties.WEAVINmG, "false");
jpaProps.put(PersistenceUnitProperties.DDL_GENERATION, PersistenceUnitProperties.CREATE_OR_EXTEND);
jpaProps.put(PersistenceUnitProperties.CDI_BEANMANAGER, new BeanManagerForEntityListeners(serviceInjectorProvider));