= Pet entity

: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 agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR  CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.


Right now our domain model still only consists of the single domain class, `PetOwner`.
We still have the `Pet` and `Visit` entities to add, along with the `PetSpecies`  enum.

include::partial$domain.adoc[]

In this set of exercises we'll focus on the `Pet` entity and its relationship with `PetOwner`.
Each `PetOwner` will hold a collection of their ``Pet``s, with actions to add or remove `Pet` instances for that collection.

[#exercise-4-1-pet-entitys-key-properties]
== Ex 4.1: Pet entity's key properties

In this exercise we'll just create the outline of the `Pet` entity, and ensure it is mapped to the database correctly.


=== Solution

[source,bash,subs="attributes+"]
----
git checkout tags/{tag-version}/04-01-pet-entity-key-properties
mvn clean install
mvn -pl webapp spring-boot:run -Dspring-boot.run.jvmArguments="-Dspring.profiles.active=dev"
----

=== Tasks

* create a meta-annotation `@PetName` for the Pet's name:
+
[source,java]
.PetName.java
----
@Property(maxLength = PetName.MAX_LEN, optionality = Optionality.MANDATORY)
@Parameter(maxLength = PetName.MAX_LEN, optionality = Optionality.MANDATORY)
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface PetName {

    int MAX_LEN = 60;
}
----

* create the `Pet` entity, using the `@PetName` meta-annotation for the `name` property:
+
[source,java]
.Pet.java
----
@Entity
@Table(
        schema= PetOwnerModule.SCHEMA,
        uniqueConstraints = {
                @UniqueConstraint(name = "Pet__owner_name__UNQ", columnNames = {"owner_id, name"})
        }
)
@EntityListeners(CausewayEntityListener.class)
@Named(PetOwnerModule.NAMESPACE + ".Pet")
@DomainObject(entityChangePublishing = Publishing.ENABLED)
@DomainObjectLayout()
@NoArgsConstructor(access = AccessLevel.PUBLIC)
@XmlJavaTypeAdapter(PersistentEntityAdapter.class)
@ToString(onlyExplicitlyIncluded = true)
public class Pet implements Comparable<Pet> {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id", nullable = false)
    @Getter @Setter
    private Long id;

    @Version
    @Column(name = "version", nullable = false)
    @PropertyLayout(fieldSetId = "metadata", sequence = "999")  // <.>
    @Getter @Setter
    private long version;


    Pet(PetOwner petOwner, String name) {
        this.petOwner = petOwner;
        this.name = name;
    }


    @ManyToOne(optional = false)
    @JoinColumn(name = "owner_id")
    @PropertyLayout(fieldSetId = "identity", sequence = "1")    // <1>
    @Getter @Setter
    private PetOwner petOwner;

    @PetName
    @Column(name = "name", length = PetName.MAX_LEN, nullable = false)
    @Getter @Setter
    @PropertyLayout(fieldSetId = "identity", sequence = "2")    // <1>
    private String name;


    private final static Comparator<Pet> comparator =
            Comparator.comparing(Pet::getPetOwner).thenComparing(Pet::getName);

    @Override
    public int compareTo(final Pet other) {
        return comparator.compare(this, other);
    }
}
----
<.> we'll look at the layout in the next exercise.

* locate the owning module, `PetOwnerModule`.
Add in a line to delete all ``Pet`` entities on teardown:
+
[source,java]
.PetOwnerModule.java
----
@Override
public FixtureScript getTeardownFixture() {
    return new TeardownFixtureJpaAbstract() {
        @Override
        protected void execute(ExecutionContext executionContext) {
            deleteFrom(Pet.class);          // <.>
            deleteFrom(PetOwner.class);
        }
    };
}
----
<.> This lne must come before the deletion of ``PetOwner``s, because (in a later exercise) the `PetOwner` entity will have child ``Pet`` entities; we always delete from the leaf level up.
+
This is used during integration tests, to reset the database after each test.


Run the application, and confirm that the application starts correctly.

Let's also confirm that JPA created the corresponding table automatically:

* Login as `secman-admin` login (password: `pass`)

* Access the menu:Prototyping[H2 Console]:
+
image::04-01/h2-console-prompt.png[]

* Connect.
There is no password for `sa`.

* Confirm the `Pet` table is created correctly:
+
image::04-01/pet-and-petowner-tables-created.png[]



[#exercise-4-2-add-pet-ui-and-layout-semantics]
== Ex 4.2: Add Pet's UI and layout semantics

Next, let's add in the UI and layout semantics for `Pet`.
At the moment we're "flying blind" because we don't have any demo `Pet` instances to see, but we can refine these files later; it's good to have some scaffolding.


=== Solution

[source,bash,subs="attributes+"]
----
git checkout tags/{tag-version}/04-02-pet-ui-and-layout-semantics
mvn clean install
mvn -pl webapp spring-boot:run -Dspring-boot.run.jvmArguments="-Dspring.profiles.active=dev"
----

=== Tasks

* annotate the `Pet#name` property with xref:refguide:applib:index/annotation/Title.adoc[@Title]:
+
[source,java]
.Pet.java
----
@PetName
@Title                                                              // <.>
@Column(name = "name", length = PetName.MAX_LEN, nullable = false)
@Getter @Setter
@PropertyLayout(fieldSetId = "identity", sequence = "2")
private String name;
----
<.> added

* create a `.layout.xml` file for `Pet` (easiest is to copy an existing one and adapt)
+
[source,xml]
.Pet.layout.xml
----
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<bs3:grid
        xsi:schemaLocation="https://causeway.apache.org/applib/layout/component https://causeway.apache.org/applib/layout/component/component.xsd https://causeway.apache.org/applib/layout/grid/bootstrap3 https://causeway.apache.org/applib/layout/grid/bootstrap3/bootstrap3.xsd"
        xmlns:cpt="https://causeway.apache.org/applib/layout/component"
        xmlns:bs3="https://causeway.apache.org/applib/layout/grid/bootstrap3"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <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="Identity">
                            <bs3:row>
                                <bs3:col span="12">
                                    <cpt:fieldSet name="Identity" id="identity"/>
                                </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:tab name="Metadata">
                            <bs3:row>
                                <bs3:col span="12">
                                    <cpt:fieldSet name="Metadata" id="metadata"/>
                                </bs3:col>
                            </bs3:row>
                        </bs3:tab>
                    </bs3:tabGroup>
                </bs3:col>
                <bs3:col span="12">
                    <cpt:fieldSet name="Details" id="details"/>
                </bs3:col>
            </bs3:row>
        </bs3:col>
        <bs3:col span="6">
            <bs3:row>
                <bs3:col span="12">
                </bs3:col>
            </bs3:row>
            <bs3:tabGroup  unreferencedCollections="true">
            </bs3:tabGroup>
        </bs3:col>
    </bs3:row>
</bs3:grid>
----
+
TIP: 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.


* next, download a suitable icon to represent the pet; name it a `Pet.png`

* create column order file, used to determine the order of columns of any actions that might be written that return a list of ``Pet``s
+
[source,text]
.Pet.columnOrder.txt
----
petOwner
name
#id
#version
----


[#exercise-4-3-add-petowners-collection-of-pets]
== Ex 4.3: Add PetOwner's collection of Pets

According to our model, ``Pet``s are owned by ``PetOwner``s:


[plantuml]
----
include::partial$skinparam.adoc[]

package pets {

    class Pet <<ppt>> {
        +id
        ..
        #petOwner
        #name
        ..
        version
    }

    class PetOwner <<role>> {
        +id
        ..
        #lastName
        #firstName
        ..
        -phoneNumber
        -emailAddress
    }
}


PetOwner *-r--> "0..*" Pet
----

In this next exercise we'll add the ``PetOwner``'s collection of ``Pet``s.


=== Solution

[source,bash,subs="attributes+"]
----
git checkout tags/{tag-version}/04-03-PetOwner-pets-collection
mvn clean install
mvn -pl webapp spring-boot:run -Dspring-boot.run.jvmArguments="-Dspring.profiles.active=dev"
----

=== Tasks

* update the `PetOwner` class, add a `pets` collection:
+
[source,java]
----
@org.apache.causeway.applib.annotation.Collection
@Getter
@OneToMany(mappedBy = "petOwner", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Pet> pets = new TreeSet<>();
----


* update `PetOwner.layout.xml` file to position the `pets` collection on the right-hand side, above the `attachment` property:
+
[source,xml]
.PetOwner.layout.xml
----
<bs3:col span="6">
    <bs3:row>
        <bs3:col span="12">
            <bs3:row>
                <bs3:col span="12">
                    <cpt:collection id="pets"/>
                </bs3:col>
            </bs3:row>
            <cpt:fieldSet name="Content" id="content">
                <cpt:property id="attachment">
                    <cpt:action id="updateAttachment" position="PANEL"/>
                </cpt:property>
            </cpt:fieldSet>
        </bs3:col>
    </bs3:row>
    <bs3:tabGroup  unreferencedCollections="true">
    </bs3:tabGroup>
</bs3:col>
----


* Create a column order file to define the order of columns in the ``PetOwner``'s `pets` collection.
It should be in the same package as `PetOwner`:
+
[source,xml]
.PetOwner#pets.columnOrder.txt
----
name
#id
#version
#petOwner
----

Run the application to confirm that the `pets` collection is visible and that the column order in the `pets` collection is correct.
(It won't have any `Pet` instances in it just yet, of course).



[#exercise-4-4-add-actions-to-add-or-remove-pets]
== Ex 4.4: Add actions to add or remove Pets

Given that a `PetOwner` knows the ``Pet``(s) that they own, it seems a reasonable responsibility to maintain this collection using behaviour on the `PetOwner`.

So in this exercise we'll add two actions on `PetOwner`: one to add a `Pet` and one to remove.

=== Solution

[source,bash,subs="attributes+"]
----
git checkout tags/{tag-version}/04-04-add-remove-pets
mvn clean install
mvn -pl webapp spring-boot:run -Dspring-boot.run.jvmArguments="-Dspring.profiles.active=dev"
----

=== Tasks

* within `PetOwner`, create the `addPet` action:
+
[source,java]
.Pet.java
----
@Action
@ActionLayout(associateWith = "pets", sequence = "1")   // <.>
public PetOwner addPet(@PetName final String name) {
    final var pet = new Pet();
    pet.setName(name);
    pet.setPetOwner(this);
    pets.add(pet);
    return this;
}
----
<.> UI hint to render button near the `pets` collection

* and create the `removePet` action:
+
[source,java]
.Pet.java
----
@Action(choicesFrom = "pets")                           // <.>
@ActionLayout(associateWith = "pets", sequence = "2")   // <.>
public PetOwner removePet(@PetName final Pet pet) {     // <1>
    pets.remove(pet);                                   // <.>
    return this;
}
----
<.> Indicates that a drop-down list of choices for the parameter should be taken from the `pets` collection
<.> UI hint to render button near the `pets` collection
<.> To delete the object, it's sufficient to simply remove from the ``PetOwner#pets`` collection, because `orphanRemoval` was set to `true`

Run the application and confirm that you can now add and remove pets for a pet owner.



[#exercise-4-5-extend-the-fixture-data-to-add-in-Pets]
== Ex 4.5: Extend the fixture data to add in Pets

In this exercise we'll extend the fixture data so that each of our pet owners have one or several pets.

=== Solution

[source,bash,subs="attributes+"]
----
git checkout tags/{tag-version}/04-05-extend-fixture-data-to-add-in-pets
mvn clean install
mvn -pl webapp spring-boot:run -Dspring-boot.run.jvmArguments="-Dspring.profiles.active=dev"
----

=== Tasks

* update the enum constants of `PetOwner_persona`, adding a fourth parameter of pet names:
+
[source,java]
.PetOwner_persona.java
----
JAMAL("Jamal Washington","jamal.pdf","J",new String[] {"Max"}),
CAMILA("Camila González","camila.pdf",null,new String[] {"Mia", "Coco", "Bella"}),
ARJUN("Arjun Patel","arjun.pdf",null,new String[] {"Rocky", "Charlie", "Buddy"}),
NIA("Nia Robinson","nia.pdf",null,new String[] {"Luna"}),
OLIVIA("Olivia Hartman","olivia.pdf",null,new String[] {"Molly", "Lucy", "Daisy"}),
LEILA("Leila Hassan","leila.pdf",null,new String[] {"Bruno"}),
MATT("Matthew Miller","matt.pdf","Matt",new String[] {"Simba"}),
BENJAMIN("Benjamin Thatcher","benjamin.pdf","Ben",new String[] {"Oliver"}),
JESSICA("Jessica Raynor","jessica.pdf","Jess",new String[] {"Milo", "Lucky"}),
DANIEL("Daniel Keating","daniel.pdf","Dan",new String[] {"Sam", "Roxy", "Smokey"});

// ...
private final String[] petNames;
----

* in the `PetOwner_persona.Builder` class, use the `petNames` to add pets to each owner:
+
[source,java]
.PetOwner_persona.java
----
@Override
protected PetOwner buildResult(final ExecutionContext ec) {

    // ...

    Arrays.stream(persona.petNames).forEach(petOwner::addPet);

    return petOwner;
}
----

Run the application and confirm that each pet owner has one or several pets associated with them.


[#exercise-4-6-add-action-validation]
== Ex 4.6: Unique pet names (action validation)

So far our pet clinic app is mostly a CRUD (create/read/update/delete) application.
Nothing wrong with that, and of course there are lots of tools and frameworks out there aside from Apache Causeway that can do this.

Where Causeway really shines though is the ease in which more complicated business logic can be implemented.

We'll see several examples of this as we flesh out the pet clinic.
In this exercise we'll introduce a simple business rule: every owner's pet must have a different name.

=== Solution

[source,bash,subs="attributes+"]
----
git checkout tags/{tag-version}/04-06-unique-pet-names-validation
mvn clean install
mvn -pl webapp spring-boot:run -Dspring-boot.run.jvmArguments="-Dspring.profiles.active=dev"
----

=== Tasks

[source,java]
.PetOwner.java
----
@MemberSupport                                                          // <.>
public String validate0AddPet(final String name) {                      // <.>
    if (getPets().stream().anyMatch(x -> Objects.equals(x.getName(), name))) {
        return "This owner already has a pet called '" + name + "'";    // <.>
    }
    return null;                                                        // <.>
}
----
<.> Indicates that this method is part of the Causeway metamodel
<.> Naming convention indicates that this is the validation of the 0^th^ parameter of `addPet`
<.> If a non-null value is returned, the framework uses its as the reason the action cannot be invoked
<.> If null is returned then the validation has succeeded.

Run the application and confirm that the validation is working as you expect.

[#exercise-4-7-add-pets-remaining-properties]
== Ex 4.7: Add Pet's remaining properties

In this exercise we'll add the remaining properties for `Pet`.
Let's remind ourselves of the domain:

[plantuml]
----
include::partial$skinparam.adoc[]

package pets {

    enum PetSpecies <<desc>> {
        Dog
        Cat
        Hamster
        Budgerigar
    }

    class Pet <<ppt>> {
        +id
        ..
        #petOwner
        #name
        ..
        -species
        -notes
        ..
        -version
    }

}

Pet  "*" -u-> PetSpecies
----

So, we need to add a `species`, and some `notes`.

=== Solution

[source,bash,subs="attributes+"]
----
git checkout tags/{tag-version}/04-07-pet-remaining-properties
mvn clean install
mvn -pl webapp spring-boot:run -Dspring-boot.run.jvmArguments="-Dspring.profiles.active=dev"
----


=== Tasks

* declare the `PetSpecies` enum:
+
[source,java]
.PetSpecies.java
----
public enum PetSpecies {
    Dog,
    Cat,
    Hamster,
    Budgerigar,
}
----

* add in a reference to `PetSpecies`:
+
[source,java]
.Pet.java
----
@Enumerated(EnumType.STRING)                                // <.>
@Column(nullable = false)
@Getter @Setter
@PropertyLayout(fieldSetId = "details", sequence = "1")
private PetSpecies species;
----
<.> mapped to a string rather than an integer value in the database

* As the `petSpecies` property is mandatory, also update the `PetOwner#addPet` action:
+
[source,java]
.PetOwner.java
----
@Action
@ActionLayout(associateWith = "pets", sequence = "1")
public PetOwner addPet(@PetName final String name, final PetSpecies species) {
    final var pet = new Pet();
    pet.setName(name);
    pet.setSpecies(species);
    pet.setPetOwner(this);
    pets.add(pet);
    return this;
}
----

* we also need to update the `PetOwner_persona.Builder`, because that uses this domain logic.
We'll use the xref:refguide:testing:index/fakedata/applib/services/FakeDataService.adoc[FakeDataService] to select a species at random:
+
[source,java]
.PetOwner_persona.java
----
Arrays.stream(persona.petNames).forEach(petName -> {
    PetSpecies randomSpecies = fakeDataService.enums().anyOf(PetSpecies.class);
    petOwner.addPet(petName, randomSpecies);
});
----

* add in an optional `notes` property:
+
[source,java]
----
@Notes
@Column(length = Notes.MAX_LEN, nullable = true)
@Getter @Setter
@Property(commandPublishing = Publishing.ENABLED, executionPublishing = Publishing.ENABLED)
@PropertyLayout(fieldSetId = "details", sequence = "2")
private String notes;
----

Let's also update the column order files:

* update the column order file for `PetOwner` (used to render standalone lists of objects returned by an action):
+
[source,text]
.Pet.columnOrder.txt
----
petOwner
name
species
#notes
#id
#version
----

* update the column order file for `Pet#pets` collection:
+
[source,text]
.PetOwner#pets.columnOrder.txt
----
name
species
#notes
#id
#version
#petOwner
----

Run the application, confirm it runs up ok and that the new properties of `Pet` are present and correct.





[#exercise-4-8-dynamic-icons-for-pet]
== Ex 4.8: Add dynamic icons for Pet

Now for a bit of UI candy.

Currently, our icon for ``Pet``s is fixed.
But we now have different species of `Pet`, so it would be nice if the icon could reflect this for each `Pet` instance as it is rendered.

This is what we'll quickly tackle in this exercise.


=== Solution

[source,bash,subs="attributes+"]
----
git checkout tags/{tag-version}/04-08-dynamic-icons
mvn clean install
mvn -pl webapp spring-boot:run -Dspring-boot.run.jvmArguments="-Dspring.profiles.active=dev"
----


=== Tasks


* download additional icons for each of the `PetSpecies` (dog, cat, hamster, budgerigar)

* 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
----
@ObjectSupport
public String iconName() {
    return getSpecies().name().toLowerCase();
}
----

Run the application.
You should find that the appropriate icon is selected based upon the species of the `Pet`.


