= PetOwner 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.


In this set of exercises we'll just focus on the `PetOwner` entity.

[#exercise-3-1-add-petowners-knownAs-property]
== Ex 3.1: Add PetOwner's knownAs property

In this exercise, we'll add a new property, `knownAs`, to capture the ``PetOwner``'s preferred name.
We'll also update the fixture script.


=== Solution

[source,bash,subs="attributes+"]
----
git checkout tags/{tag-version}/03-01-adds-PetOwner-knownAs-property
mvn clean install
mvn -pl webapp spring-boot:run
----

Use the menu:Prototyping[Fixture Scripts] menu to setup some example data.


=== Tasks

* add a `knownAs` property:
+
[source,java]
.PetOwner.java
----
@Column(length = 40, nullable = true, name = "knownAs")     // <.>
@Getter @Setter
@Property(editing = Editing.ENABLED)                        // <.>
@PropertyLayout(
        fieldSetId = LayoutConstants.FieldSetId.IDENTITY,   // <.>
        sequence = "1.1"                                    // <.>
)
private String knownAs;
----
<.> JPA annotation, declared to be `nullable` because we don't necessarily have a value
<.> The xref:refguide:applib:index/annotation/Property.adoc[@Property] annotation provides domain semantics.
(In comparison, the xref:refguide:applib:index/annotation/PropertyLayout.adoc[@PropertyLayout] annotation defines UI hints/semantics.
Directly editable (similar to the "notes" property)
<.> Renders this new property in the same field set as `name` ...
<.> ... and after the `name`, not before it
+
It's also possible to define UI semantics through the associated `.layout.xml` file; we'll look at that alternative in a later exercise.

* update `PetOwner_persona` to specify an `knownAs` for some of the owners:
+
[source,java]
.PetOwner_persona.java
----
public enum PetOwner_persona
implements Persona<PetOwner, PetOwner_persona.Builder> {

    JAMAL("Jamal Washington", "jamal.pdf", "J"),
    CAMILA("Camila González", "camila.pdf", null),
    ARJUN("Arjun Patel", "arjun.pdf", null),
    NIA("Nia Robinson", "nia.pdf", null),
    OLIVIA("Olivia Hartman", "olivia.pdf", null),
    LEILA("Leila Hassan", "leila.pdf", null),
    MATT("Matthew Miller", "matt.pdf", "Matt"),
    BENJAMIN("Benjamin Thatcher", "benjamin.pdf", "Ben"),
    JESSICA("Jessica Raynor", "jessica.pdf", "Jess"),
    DANIEL("Daniel Keating", "daniel.pdf", "Dan");

    private final String name;
    private final String contentFileName;
    private final String knownAs;

    ...
}
----

* for the builder, use the `knownAs` value of the entity once created:
+
[source,java]
.PetOwner_persona.java
----
@Override
protected PetOwner buildResult(final ExecutionContext ec) {

    val petOwner = wrap(PetOwners).create(persona.name);

    // ...

    if (persona.knownAs != null) {
        petOwner.setKnownAs(persona.knownAs);
    }

    // ...
}
----



[#exercise-3-2-define-PetOwners-title-imperatively]
== Ex 3.2: Define PetOwner's title imperatively

Every domain object has a title, allowing end-users to distinguish one object instance from another.
There's no requirement for this title to be unique, but it does need to be unique "enough".

Our app currently declares the title of `PetOwner` declaratively using the xref:refguide:applib:index/annotation/Title.adoc[@Title] annotation on the `name` property.
In this exercise, let's change that to also include the `knownAs` property as defined.
Our rule will be:

* if there is a `knownAs`, then the title should be "<name> (<knownAs>)"
* but if `knownAs` is empty, then the title should be simply the `name`, as it is now.

To do that, we need to define the title imperatively, using the xref:refguide:applib-methods:ui-hints.adoc#title[title()] method.


=== Solution

[source,bash,subs="attributes+"]
----
git checkout tags/{tag-version}/03-02-define-PetOwner-title-imperatively
mvn clean install
mvn -pl webapp spring-boot:run
----

=== Tasks

* locate the `name` property of `PetOwner`, and remove the `@Title` annotation

* add a new `title()` method:
+
[source,java]
.PetOwner.java
----
@ObjectSupport      // <.>
public String title() {
    return getName() + (getKnownAs() != null ? " (" + getKnownAs() + ")" : "");
}
----
<.> The xref:refguide:applib:index/annotation/ObjectSupport.adoc[@ObjectSupport] annotation tells the framework to include this method in the metamodel.



[#exercise-3-3-remaining-PetOwner-properties]
== Ex 3.3: Add remaining `PetOwner` properties

Comparing `PetOwner` as it is currently defined with our domain model, there are a couple of changes still to make:

* we need to rename `lastCheckedIn` property to `lastVisit` property

* we still need to add in a `telephoneNumber` property, and an `emailAddress` property

We'll make these changes in this exercise.

=== Solution

[source,bash,subs="attributes+"]
----
git checkout tags/{tag-version}/03-03-remaining-PetOwner-properties
mvn clean install
mvn -pl webapp spring-boot:run
----

=== Tasks

Rename the `lastCheckedIn` property to `lastVisit`:

* rename the field in `PetOwner`
* update `PetOwner_persona` also (but your IDE probably refactored this already).
* to make it more realistic, let's change the fixture script so that the value of `lastVisit` is some number of days in the past:
+
[source,java]
.PetOwner_persona.java
----
final var numDaysAgo = fakeDataService.ints().between(100, 2);                          // <.>
final var lastVisit = clockService.getClock().nowAsLocalDate().minusDays(numDaysAgo);   // <.>
petOwner.setLastVisit(lastVisit);
----
<.> The xref:refguide:testing:index/fakedata/applib/services/FakeDataService.adoc[FakeDataService] provides an easy way to create fake data for testing and prototyping
<.> It's good practice to use xref:refguide:applib:index/services/clock/ClockService.adoc[ClockService], so it can be easily mocked in tests

Now add the two new properties:

* Add the `telephoneNumber` property:
+
[source,java]
.PetOwner.java
----
@Column(length = 40, nullable = true, name = "telephoneNumber") // <.>
@Getter @Setter
@Property(editing = Editing.ENABLED)
@PropertyLayout(fieldSetId = "contact", sequence = "1.1")   // <.>
private String telephoneNumber;
----
<.> The JPA `@Column` annotation indicates that the property is optional in the database; Causeway also understands this for the domain layer.
<.> This places the property in a new "contact" fieldset group; we'll define that below

* and add the `emailAddress` property:
+
[source,java]
.PetOwner.java
----
@Column(length = 40, nullable = true, name = "emailAddress")
@Getter @Setter
@Property(editing = Editing.ENABLED)
@PropertyLayout(fieldSetId = "contact", sequence = "1.2")
private String emailAddress;
----

And now let's define the referenced "contact" fieldset.

* Locate the associated `.layout.xml` file for `PetOwner`.
Before the "Details" fieldset, add:
+
[source,xml]
.PetOwner.layout.xml
----
<bs3:col span="12">
    <cpt:fieldSet name="Contact" id="contact"/>
</bs3:col>
----

TIP: if you make changes to this file then your IDE may be able detect the changes automatically.
For example, with IntelliJ you can use menu:Run[Debugging Actions > Reload Changed Classes].

You can learn more about layout files xref:userguide::ui-layout-and-hints.adoc[here].

[#exercise-3-4-list-new-properties-of-PetOwner]
== Ex 3.4: List new properties of PetOwner

Several of the actions in the "Pet Owners" menu return lists of ``PetOwner``s; the "list" action returns all of them.

When we do this we see only the `name` property of the `PetOwner`.
Let's update the app to see some other properties we added in the previous exercise.

=== Solution

[source,bash,subs="attributes+"]
----
git checkout tags/{tag-version}/03-04-list-new-properties-of-PetOwner
mvn clean install
mvn -pl webapp spring-boot:run
----

=== Tasks

* Locate the `PetOwner.columnOrder.txt` and make these changes:
+
[source,text]
.PetOwner.columnOrder.txt
----
name
telephoneNumber
lastVisit
emailAddress
----

Confirm that menu:Pet Owners[List All] now shows the additional properties as columns.

TIP: if you make changes to this file then your IDE may be able detect the changes automatically.
For example, with IntelliJ you can use menu:Run[Debugging Actions > Reload Changed Classes].

You can learn more about layout files xref:userguide::ui-layout-and-hints.adoc[here].




[#exercise-3-5-initial-fixture-script]
== Ex 3.5: Initial Fixture Script

As we prototype with an in-memory database, it means that we need to setup the database each time we restart the application.
Using the menu:Prototyping[Fixture Scripts] menu to setup data saves some time, but it would nicer still if that script could be run automatically.
We can do that by specifying a configuration property.

We can also leverage link:https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.profiles[Spring Boot profiles] to keep this configuration separate.


=== Solution

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


=== Tasks

* locate the `application.yml` configuration file (in the webapp module) and create this new file alongside it:
+
[source,yaml]
.application-dev.yml
----
causeway:
  testing:
    fixtures:
      initial-script: domainapp.webapp.application.fixture.scenarios.DomainAppDemo
----

* modify the startup of your application to enable this profile, using this system prpoerty:
+
[source]
----
-Dspring.profiles.active=dev
----

We also need to make one small modification to the fixture script itself.
The initial fixtures are run with a built-in user called "\__system", that has no roles and therefore no permissions.
We either need to change the fixture script to run as a different user that does have the permissions (there's a service called xref:refguide:applib:index/services/sudo/SudoService.adoc[] that would let us do that), or, simpler, just temporarily switch off permission checks.
We'll go with the second option:

* locate the `PetOwner_persona.Builder` class, and make this change:
+
[source,java]
.PetOwner_persona.java
----
@RequiredArgsConstructor
public enum PetOwner_persona /*...*/ {

    @Accessors(chain = true)
    public static class Builder extends BuilderScriptWithResult<PetOwner> {

        @Override
        protected PetOwner buildResult(final ExecutionContext ec) {

            val petOwner = petOwners.create(persona.name); // <.>

            // ...
        }
        // ...
    }
    // ...
}
----
<.> Previously this was `wrap(petOwners).create(...)`.
The `wrap(...)` method uses the xref:refguide:applib:index/services/wrapper/WrapperFactory.adoc[] to wrap the domain object in a proxy, and the proxy enforces all the business rules, including visibility.

When you now run the application you should now find that there are 10 `PetOwner` objects already created.


[#exercise-3-6-update-home-page-to-show-PetOwners]
== Ex 3.6: Update Home Page to show PetOwners

Every Causeway app can nominate a home page, basically a xref:userguide::view-models.adoc[view model] that's been annotated with xref:refguide:applib:index/annotation/HomePage.adoc[].
Currently the home page for our app is the one we inherited from the starter app, showing a list of ``SimpleObject``s.

In this exercise, we'll refactor the home page view model to show a list of ``PetOwner``s instead.


=== Solution

[source,bash,subs="attributes+"]
----
git checkout tags/{tag-version}/03-06-update-home-page-to-show-pet-owners
mvn clean install
mvn -pl webapp spring-boot:run -Dspring-boot.run.jvmArguments="-Dspring.profiles.active=dev"
----


=== Tasks

Locate the `HomePageViewModel` class:

* inject `PetOwners` domain service (instead of `SimpleObjects`)
* change the `title()` implementation
* rename the collection from `getObjects()` to `getPetOwners()`

The positioning of the collection is also specified in the corresponding layout file, and so that file also needs updating.

* locate the `HomePageViewModel.layout.xml` file, and update accordingly
+
[source,xml]
.HomePageViewModel.layout.xml
----
<bs3:col span="6" unreferencedCollections="true">
    <bs3:row>
        <bs3:col span="12">
            <cpt:collection id="petOwners" defaultView="table"/>
        </bs3:col>
    </bs3:row>
</bs3:col>
----

By default this will show all of the properties of `PetOwner`.

image::03-06/home-page-default-columns.png[]


We can change this by creating a file `HomePageViewModel#petOwners.columnOrder.txt`, alongside the `HomePageViewModel`.

[source,txt]
.HomePageViewModel#petOwners.columnOrder.txt
----
name
telephoneNumber
emailAddress
#attachment
#lastVisit
#knownAs
#version
----

TIP: the action "Download .columnOrder.txt files (ZIP)" (available only when prototyping) provides an easy way to obtain this file; you can then update as required.



[#exercise-3-7-modify-the-menu-action-that-creates-petowners]
== Ex 3.7: Modify the menu action that creates PetOwners

If we want to create a new `PetOwner` and specify additional details, at the moment it's a two stage process: create the `PetOwner` (using menu:PetOwners[create]), then set the additional details afterwards.

In this exercise we'll simplify that workflow by allowing the additional details to optionally be specified during the create.

=== Solution

[source,bash,subs="attributes+"]
----
git checkout tags/{tag-version}/03-07-modifies-PetOwners-create-action
mvn clean install
mvn -pl webapp spring-boot:run -Dspring-boot.run.jvmArguments="-Dspring.profiles.active=dev"
----

=== Tasks

* update `PetOwners#create()` method, to allow the additional details to optionally be specified:
+
[source,java]
.PetOwners.java
----
@Action(semantics = SemanticsOf.NON_IDEMPOTENT)
@ActionLayout(promptStyle = PromptStyle.DIALOG_SIDEBAR)
public PetOwner create(
        @Name final String name,
        @Parameter(maxLength = 40, optionality = Optionality.OPTIONAL)
        final String knownAs,
        @Parameter(maxLength = 40, optionality = Optionality.OPTIONAL)
        final String telephoneNumber,
        @Parameter(maxLength = 40, optionality = Optionality.OPTIONAL)
        final String emailAddress) {
    final var petOwner = PetOwner.withName(name);
    petOwner.setKnownAs(knownAs);
    petOwner.setTelephoneNumber(telephoneNumber);
    petOwner.setEmailAddress(emailAddress);
    return repositoryService.persist(petOwner);
}
----

* also update `PetOwner_persona.Builder` fixture script, passing in `null` for the new parameters:
+
[source,java]
.PetOwner_persona.java
----
val petOwner = PetOwners.create(persona.name, null, null, null);
----
+
(Or, even better - pass in `knownAs` as the 2^nd^ parameter, and remove the explicit setting of this value later in the fixture script)

When you run the app, confirm that only the `name` parameter is optional:

image::03-07/new-parameters-are-optional.png[width=80%]


[#exercise-3-8-prompt-styles]
== Ex 3.8: Prompt styles

The framework provides many ways to customise the UI, either through the layout files or using the `@XxxLayout` annotations.
Default UI conventions can also be specified using the `application.yml` configuration file.

In this exercise we'll change the prompt style for both a service (menu) action, ie menu:PetOwners[create], and an object action, ie `PetOwner#updateName`.


=== Solution

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


=== Tasks

* Service (menu) actions are always shown in a dialog, of which there are two styles: modal prompt, or sidebar.
If not specified explicitly, they will default to dialog modal.
+
Therefore, remove the `@ActionLayout(promptStyle)` for `PetOwners#create`
+
[source,java]
.PetOwners.java
----
@Action(semantics = SemanticsOf.NON_IDEMPOTENT)
// @ActionLayout(promptStyle = PromptStyle.DIALOG_SIDEBAR)
public PetOwner create( ... ) { ... }
----
+
Confirm that the dialog is now shown as a modal prompt.

* Object actions can be shown either inline or in a dialog, but default to inline.
If forced to use a dialog, then they default to a sidebar prompt rather than a modal prompt.
+
Remove the `@ActionLayout(promptStyle)` for `PetOwner#updateName`:
+
[source,java]
.PetOwner.java
----
@Action( ... )
@ActionLayout(
        ...
        // promptStyle = PromptStyle.INLINE,
        ...
)
public PetOwner updateName( ... ) { ... }
----
+
Confirm that prompt is still inline.

* Using a configuration property we can change the default for object actions to use a dialog rather than inline.
We'll use the "dev" profile introduced earlier:
+
[source,yaml]
.application-dev.yaml
----
causeway:
  viewer:
    wicket:
      prompt-style: dialog
----
+
Remember to activate this new profile (`-Dspring.profiles.active=dev`) and confirm that the `updateName` prompt now uses a sidebar dialog.

* We can overide the default dialog style for both service and object actions using further configuration properties.
+
Switch the defaults so that service actions prefer to use a sidebar dialog, while object actions would use a modal dialog:
+
[source,yaml]
.application-dev.yaml
----
causeway:
  viewer:
    wicket:
      prompt-style: dialog
      dialog-mode: modal
      dialog-mode-for-menu: sidebar
----

* Optional: use `@ActionLayout(promptStyle=...)` to override these defaults.
+
Be aware that "inline" makes no sense/is not supported for service actions.

* Finish off the exercises by setting up these defaults to retain the original behaviour:
+
[source,yaml]
.application-dev.yaml
----
causeway:
  viewer:
    wicket:
      prompt-style: inline
      #dialog-mode: modal   # unused if prompt-style is inline
      dialog-mode-for-menu: sidebar
----





[#exercise-3-9-derived-days-since-last-visit-property]
== Ex 3.9: Derived 'days since last visit' property

Not every property has to persisted, nor editable (indeed most properties are not editable).

For example, it might be useful to calculate the number of days since the pet owner last visited; perhaps for marketing purposes.

In this exercise we'll see how easy it is to create such a derived property.


=== Solution

[source,bash,subs="attributes+"]
----
git checkout tags/{tag-version}/03-09-derived-days-since-last-visit-property
mvn clean install
mvn -pl webapp spring-boot:run -Dspring-boot.run.jvmArguments="-Dspring.profiles.active=dev"
----


=== Tasks

Locate the `PetOwner` class:

* inject an instance of xref:refguide:applib:index/services/clock/ClockService.adoc[]:
+
[source,java]
.PetOwner.java
----
@Inject
@Transient                  // <.>
ClockService clockService;
----
<.> instructs JPA that this field is not persisted
+
Note that Apache Causeway allows services to be injected into entities (actually, into pretty much any domain object)

* implement `getDaysSinceLastVisit()` method, calculating the number of days since "today".
+
[source,java]
.PetOwner.java
----
@Property
@PropertyLayout(fieldSetId = LayoutConstants.FieldSetId.DETAILS, sequence = "3.1")  // <.>
public long getDaysSinceLastVisit() {
    return getLastVisit() != null
            ? ChronoUnit.DAYS.between(getLastVisit(), clockService.getClock().nowAsLocalDate())
            : null;
}
----
<.> positioned just after the `lastVisit` property

*  update `PetOwner.columnOrder.txt` to indicate whether this new property should be rendered in standalone tables (returned from finder actions):
+
[source,text]
.PetOwner.columnOrder.txt
----
name
telephoneNumber
emailAddress
daysSinceLastVisit
#lastVisit
#knownAs
#attachment
#notes
#version
----

*  similarly, update `HomePageViewModel#petOwners.columnOrder.txt` to indicate whether this new property should be rendered on the home page:
+
[source,text]
.HomePageViewModel#petOwners.columnOrder.txt
----
name
telephoneNumber
emailAddress
daysSinceLastVisit
#lastVisit
#knownAs
#attachment
#notes
#version
----



[#exercise-3-10-use-meta-annotations-to-reduce-duplication]
== Ex 3.10: Use meta-annotations to reduce duplication

There is some duplication between menu:PetOwners[create] action and the `PetOwner` class: both define `name` as a  parameter or property respectively, and they also share a number of other parameters/properties, `telephoneNumber`, `emailAddress` and `knownAs`.

With `name` you might have noticed that the `@Name` meta-annotation that came with the starter, and which centralizes the domain knowledge about what a `name` is.

In this exercise we'll use the same approach, introducing a meta-annotation to centralize semantics for  `telephoneNumber`.
(We won't do `emailAddress` or `knownAs` though - we'll explore an even more powerful way to reduce duplication in a following exercise).

=== Solution

[source,bash,subs="attributes+"]
----
git checkout tags/{tag-version}/03-10-use-meta-annotations-to-reduce-duplication
mvn clean install
mvn -pl webapp spring-boot:run -Dspring-boot.run.jvmArguments="-Dspring.profiles.active=dev"
----

=== Task

* Create a `@PhoneNumber` meta-annotation, defined to be an editable property:
+
[source,java]
.PhoneNumber.java
----
@Property(
        editing = Editing.ENABLED,
        maxLength = PhoneNumber.MAX_LEN,
        optionality = Optionality.OPTIONAL
)
@Parameter(
        maxLength = PhoneNumber.MAX_LEN,
        optionality = Optionality.OPTIONAL
)
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface PhoneNumber {

    int MAX_LEN = 40;
}
----

* update the `telephoneNumber` of `PetOwner` to use the meta-annotation:
+
[source,java]
.PetOwner.java
----
@PhoneNumber                                                                        // <.>
@Column(length = PhoneNumber.MAX_LEN, nullable = true, name = "telephoneNumber")    // <.>
@Getter @Setter
@PropertyLayout(fieldSetId = "contact", sequence = "1.1")                           // <.>
private String telephoneNumber;
----
<.> updated to use meta-annotation
<.> The JPA implementation used by Apache Causeway (EclipseLink) does not support meta-annotations, so the field must still be annotated with `@Column`.
We can at least use the `PhoneNumber.MAX_LEN` for the length.
<.> Any annotations defined at the field level supplement or override those inherited from the meta-annotation.

* and update the `PetOwners#create()` action method also:
+
[source,java]
.PetOwners.java
----
@Action(semantics = SemanticsOf.NON_IDEMPOTENT)
// @ActionLayout(promptStyle = PromptStyle.DIALOG_SIDEBAR)
public PetOwner create(
        @Name final String name,
        @Parameter(maxLength = 40, optionality = Optionality.OPTIONAL)
        final String knownAs,
        @PhoneNumber                        // <.>
        final String telephoneNumber,
        @Parameter(maxLength = 40, optionality = Optionality.OPTIONAL)
        final String emailAddress) {
        // ...
}
----
<.> updated to use meta-annotation




[#exercise-3-11-validation]
== Ex 3.11: Validation

At the moment there are no constraints for the format of `telePhoneNumber` properties.
We can fix this by adding rules to their respective meta-annotations.


=== Solution

[source,bash,subs="attributes+"]
----
git checkout tags/{tag-version}/03-11-validation-rules-using-metaannotations
mvn clean install
mvn -pl webapp spring-boot:run -Dspring-boot.run.jvmArguments="-Dspring.profiles.active=dev"
----

=== Task


* Update the `@Property` and `@Parameter` annotations of the `@PhoneNumber` meta-annotation:
+
[source,java]
.PhoneNumber.java
----
@Property(
        editing = Editing.ENABLED,
        maxLength = PhoneNumber.MAX_LEN,
        optionality = Optionality.OPTIONAL,
        regexPattern = PhoneNumber.REGEX_PATTERN,                           // <.>
        regexPatternReplacement = PhoneNumber.REGEX_PATTERN_REPLACEMENT     // <.>
)
@Parameter(
        maxLength = PhoneNumber.MAX_LEN,
        optionality = Optionality.OPTIONAL,
        regexPattern = PhoneNumber.REGEX_PATTERN,                           // <1>
        regexPatternReplacement = PhoneNumber.REGEX_PATTERN_REPLACEMENT     // <2>
)
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface PhoneNumber {

    int MAX_LEN = 40;
    String REGEX_PATTERN = "[+]?[0-9 ]+";
    String REGEX_PATTERN_REPLACEMENT =
            "Specify only numbers and spaces, optionally prefixed with '+'.  " +
            "For example, '+353 1 555 1234', or '07123 456789'";

}
----
<.> regex constraint
<.> validation message if the constraint is not met

Try out the application and check that these rules are applied.


[#exercise-3-12-more-validation]
== Ex 3.12: More validation

The `updateName` action also has a validation rule, applied directly to the method:

[source,java]
.PetOwner.java
----
public String validate0UpdateName(String newName) {             // <.>
    for (char prohibitedCharacter : "&%$!".toCharArray()) {
        if( newName.contains(""+prohibitedCharacter)) {
            return "Character '" + prohibitedCharacter + "' is not allowed.";
        }
    }
    return null;
}
----
<.> The xref:refguide:applib-methods:prefixes.adoc#validate[validate...()] supporting method is used to validate parameters; in this case the "0^th^" parameter of `updateName`.
More details on the validate supporting method can be found xref:refguide:applib-methods:prefixes.adoc#validate[here].

In this exercise we'll move this constraint onto the `@Name` meta-annotation instead, using a xref:refguide:applib:index/spec/Specification.adoc[].


=== Solution

[source,bash,subs="attributes+"]
----
git checkout tags/{tag-version}/03-12-moves-validation-onto-metaannotation
mvn clean install
mvn -pl webapp spring-boot:run -Dspring-boot.run.jvmArguments="-Dspring.profiles.active=dev"
----

=== Task

*  Update the `@Name` meta-annotation using a xref:refguide:applib-classes:spec.adoc#specification[Specification]:
+
[source,java]
.Name.java
----
@Property(maxLength = Name.MAX_LEN, mustSatisfy = Name.Spec.class)      // <.>
@Parameter(maxLength = Name.MAX_LEN, mustSatisfy = Name.Spec.class)     // <1>
@ParameterLayout(named = "Name")
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Name {

    int MAX_LEN = 40;
    String PROHIBITED_CHARACTERS = "&%$!";

    class Spec extends AbstractSpecification<String> {                  // <.>
        @Override public String satisfiesSafely(String candidate) {
            for (char prohibitedCharacter : PROHIBITED_CHARACTERS.toCharArray()) {
                if( candidate.contains(""+prohibitedCharacter)) {
                    return "Character '" + prohibitedCharacter + "' is not allowed.";
                }
            }
            return null;
        }
    }
}
----
<.> indicates that the property or parameter value must satisfy the specification below
<.> defines the specification definition, where a non-null value is the reason why the specification is not satisfied.

* Remove the `validate0UpdateName` method and `PROHIBITED_CHARACTERS` constant from `PetOwner`.

* update the `@ActionLayout#describedAs` annotation for "updateName" to use `Name.PROHIBITED_CHARACTERS`

Test the app once more.

NOTE: in making this refactoring we actually fixed a bug: there was no validation of the parameter when a new `PetOwner` was created; but now there is.




[#exercise-3-13-scalar-custom-value-types]
== Ex 3.13: Scalar custom value types

We could use meta-annotations for the "emailAddress" property and parameter, but instead we'll reduce duplication using an even more powerful technique, namely custom value types.
We'll define a custom class `EmailAddress` with value semantics, allowing validation and any other behaviour to move onto the custom class itself.

Apache Causeway supports both scalar and composite value types.
For email address, we'll use a single string, so it's a scalar value type.

=== Solution

[source,bash,subs="attributes+"]
----
git checkout tags/{tag-version}/03-13-scalar-custom-value-type-for-email-address
mvn clean install
mvn -pl webapp spring-boot:run -Dspring-boot.run.jvmArguments="-Dspring.profiles.active=dev"
----

=== Task

* Define the `EmailAddress` value type:
+
[source,java]
.EmailAddress.java
----
@javax.persistence.Embeddable                                           // <.>
@org.apache.causeway.applib.annotation.Value                            // <.>
@lombok.EqualsAndHashCode                                               // <.>
public class EmailAddress implements Serializable {                     // <.>

    static final int MAX_LEN = 100;
    static final int TYPICAL_LEN = 30;
    static final Pattern REGEX =
        Pattern.compile("^[\\w-\\+]+(\\.[\\w]+)*@[\\w-]+(\\.[\\w]+)*(\\.[a-zA-Z]{2,})$");

    public static EmailAddress of(String value) {                       // <.>
        if (_Strings.isNullOrEmpty(value)) {
            return null;
        }
        if(!EmailAddress.REGEX.matcher(value).matches()) {
            throw new RuntimeException("Invalid email format");
        }

        final var ea = new EmailAddress();
        ea.value = value;
        return ea;
    }

    protected EmailAddress() {}                                         // <.>

    @Getter
    @Column( length = MAX_LEN, nullable = true, name = "emailAddress")  // <1>
    String value;                                                       // <.>
}
----
<.> Required JPA annotations
<.> Indicates to Causeway that this class is a value type (as opposed to an entity, view model or domain service)
<.> Value types generally implement equals and hashCode
<.> Value types generally are serializable
<.> Validation moves to the factory method
<.> no-arg constructor is required by JPA
<.> The single data attribute to be persisted

* Implement a "value semantics provider".
This tells Causeway how to interact with the value type
+
[source,java]
.EmailAddressValueSemantics.java
----
@Named(PetOwnerModule.NAMESPACE + ".EmailAddressValueSemantics")
@Component                                                                  // <.>
public class EmailAddressValueSemantics
        extends ValueSemanticsAbstract<EmailAddress> {                      // <.>

    @Override
    public Class<EmailAddress> getCorrespondingClass() {
        return EmailAddress.class;
    }

    @Override
    public ValueType getSchemaValueType() {                                 // <.>
        return ValueType.STRING;
    }

    @Override
    public ValueDecomposition decompose(final EmailAddress value) {         // <.>
        return decomposeAsNullable(value, EmailAddress::getValue, ()->null);
    }

    @Override
    public EmailAddress compose(final ValueDecomposition decomposition) {   // <4>
        return composeFromNullable(
                decomposition, ValueWithTypeDto::getString, EmailAddress::of, ()->null);
    }

    @Override
    public DefaultsProvider<EmailAddress> getDefaultsProvider() {           // <.>
        return () -> null;
    }

    @Override
    public Renderer<EmailAddress> getRenderer() {                           // <.>
        return (context, emailAddress) ->  emailAddress == null ? null : emailAddress.getValue();
    }

    @Override
    public Parser<EmailAddress> getParser() {                               // <.>
        return new Parser<>() {

            @Override
            public String parseableTextRepresentation(Context context, EmailAddress emailAddress) {
                return renderTitle(emailAddress, EmailAddress::getValue);
            }

            @Override
            public EmailAddress parseTextRepresentation(Context context, String text) {
                return EmailAddress.of(text);
            }

            @Override
            public int typicalLength() {
                return EmailAddress.TYPICAL_LEN;
            }

            @Override
            public int maxLength() {
                return EmailAddress.MAX_LEN;
            }
        };
    }

    @Override
    public IdStringifier<EmailAddress> getIdStringifier() {                 // <.>
        return new IdStringifier.EntityAgnostic<>() {
            @Override
            public Class<EmailAddress> getCorrespondingClass() {
                return EmailAddressValueSemantics.this.getCorrespondingClass();
            }

            @Override
            public String enstring(@NonNull EmailAddress value) {
                return _Strings.base64UrlEncode(value.getValue());
            }

            @Override
            public EmailAddress destring(@NonNull String stringified) {
                return EmailAddress.of(_Strings.base64UrlDecode(stringified));
            }
        };
    }
}
----
<.> Defined as a Spring `@Component` so that the framework can discover and use this value semantics provider
<.> Typically inherit from `ValueSemanticsAbstract`, a convenience superclass
<.> The `schemaValueType` in essence defines the widget that will be used to interact with the value
<.> The `ValueDecomposition` is primarily used by the xref:vro::about.adoc[] to convert to/from JSON.
<.> The `DefaultsProvider` provides an initial value, if any.
For some values there is often a reasonable default, eg `0` for a number, or `[0,0]` for a coordinate, or today's date.
<.> The `Renderer` provides string and if required HTML representations of the value
<.> The `Parser` converts string representations into the value.
Note how this code delegates back to the `EmailAddress` value type itself
<.> The `IdStringifier` returns a string representation of the value, in case it is used as an identifier of the object.
The returned string would appear in URLs or bookmarks, for example.

* update `PetOwner#emailAddress` to use the `EmailAddress` value type:
+
[source,java]
.PetOwner.java
----
@javax.persistence.Embedded                                                 // <.>
@Getter @Setter
@Property(editing = Editing.ENABLED, optionality = Optionality.OPTIONAL)    // <.>
@PropertyLayout(fieldSetId = "contact", sequence = "1.2")
private EmailAddress emailAddress;                                          // <.>
----
<.> required JPA annotation
<.> need to explicitly indicate that this property is optional (previously it was inferred from `@Column(nullable=)`)
<.> change the type from `String` to `EmailAddress`

* update `PetOwners#create` to use the `EmailAddress` value type:
+
[source,java]
.PetOwner.java
----
@Action(semantics = SemanticsOf.NON_IDEMPOTENT)
// @ActionLayout(promptStyle = PromptStyle.DIALOG_SIDEBAR)
public PetOwner create(
        @Name final String name,
        @Parameter(maxLength = 40, optionality = Optionality.OPTIONAL)
        final String knownAs,
        @PhoneNumber
        final String telephoneNumber,
        @Parameter(optionality = Optionality.OPTIONAL)
        final EmailAddress emailAddress) {                  // <.>
    final var petOwner = PetOwner.withName(name);
    petOwner.setKnownAs(knownAs);
    petOwner.setTelephoneNumber(telephoneNumber);
    petOwner.setEmailAddress(emailAddress);
    return repositoryService.persist(petOwner);
}
----
<.> Change the parameter' type from `String` to `EmailAddress`

Run the application and try to enter an invalid email address; the logic in the value type should prevent this.


[#exercise-3-14-use-layout-xml-file-for-ui-semantics]
== Ex 3.14: Use layout xml file for UI semantics

At the moment the associated `.layout.xml` file for `PetOwner` is used to define rows, columns and fieldsets, while the xref:refguide:applib:index/annotation/PropertyLayout.adoc[@PropertyLayout] annotation is grouped to associate properties with those fieldsets.

If we prefer, we can specify this association within the `PetOwner.layout.xml` file instead.
And we can also do the same thing associating actions with properties or collections.
This has the benefit of being dynamic; we can move fields around in the layout without having to recompile/restart the application.


=== Solution

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

=== Task

Associate properties with fieldsets using `.layout.xml`:

* associate `name` property with `identity` fieldset:

** remove `@PropertyLayout` from `PetOwner`:
+
[source,java]
.PetOwner.java
----
@Name
@Column(length = Name.MAX_LEN, nullable = false, name = "name")
@Getter @Setter @ToString.Include
// @PropertyLayout(fieldSetId = LayoutConstants.FieldSetId.IDENTITY, sequence = "1")
private String name;
----

** add to `PetOwner.layout.xml`:
+
[source,xml]
.PetOwner.layout.xml
----
<cpt:fieldSet name="Identity" id="identity">
    <cpt:property id="name"/>
</cpt:fieldSet>
----

* similarly for `knownAs`
* similarly for `telephoneNumber`
* similarly for `emailAddress`
* similarly for `notes`
* similarly for `lastVisit`
* similarly for `daysSinceLastVisit`
* similarly for `attachment`

* associate `version` property with `metadata` fieldset, and also explicitly specify the location of the two framework-provided properties that also reside in that fieldset:

** change ``PetOwner#version` property to::
+
[source,java]
.PetOwner.java
----
@Version
@Column(name = "version", nullable = false)
//@PropertyLayout(fieldSetId = "metadata", sequence = "999")    // <.>
@Property                                                       // <.>
@Getter @Setter
private long version;
----
<.> The `@PropertyLayout` was removed...
<.> ... but `@Property` has been added instead.
+
This is because at least one of @Property` or `@PropertyLayout` must be present to identify the field as a property (at least so far as this application has been configured.
In fact, it is possible to configure the framework to be either less strict or more strict with respect to whether annotations are specified, see xref:refguide:config:sections/causeway.core.meta-model.introspector.adoc#causeway.core.meta-model.introspector.policy[]]).

** add to `PetOwner.layout.xml`:
+
[source,xml]
.PetOwner.layout.xml
----
<cpt:fieldSet name="Metadata" id="metadata">
    <cpt:property id="logicalTypeName"/>        // <.>
    <cpt:property id="objectIdentifier"/>       // <1>
    <cpt:property id="version"/>
</cpt:fieldSet>
----
<.> framework-provided properties.
If we want the `version` property to appear last in the fieldset, then we need to specify these other properties also.


* associate the `updateName` action with the `name` property

** remove from `PetOwner`:
+
[source,java]
.PetOwner.java
----
    @Action( ... )
    @ActionLayout(
            // associateWith = "name",      // <.>
            ...
    )
    public PetOwner updateName(
            @Name final String name) { ... }
----
<.> deleted this line

** add to `PetOwner.layout.xml`:
+
[source,xml]
.PetOwner.layout.xml
----
<cpt:property id="name">
    <cpt:action id="updateName"/>
</cpt:property>
----

* similarly `updateAttachment` action

** remove from `PetOwner`:
+
[source,java]
.PetOwner.java
----
@Action( ... )
// @ActionLayout(                               // <.>
//      associateWith = "attachment",           // <1>
//      position = ActionLayout.Position.PANEL  // <1>
// )                                            // <1>
public PetOwner updateAttachment( ... ) { ... }
----
<.> deleted lines

** add to `PetOwner.layout.xml`:
+
[source,xml]
.PetOwner.layout.xml
----
<cpt:property id="attachment">
    <cpt:action id="updateAttachment" position="PANEL"/>
</cpt:property>
----


** remove from `PetOwner`:
+
[source,java]
.PetOwner.java
----
@Action( ... )
@ActionLayout(
    // fieldSetId = LayoutConstants.FieldSetId.IDENTITY,        // <.>
    // position = ActionLayout.Position.PANEL,                  // <1>
    describedAs = "Deletes this object from the persistent datastore"
)
public void delete() { ... }
----
<.> deleted lines

* add to `PetOwner.layout.xml`:
+
[source,xml]
.PetOwner.layout.xml
----
<cpt:fieldSet name="Identity" id="identity">
    <cpt:action id="delete" position="PANEL"/>  <!--.-->
    <cpt:property id="name">
        <cpt:action id="updateName"/>
    </cpt:property>
    ...
</cpt:fieldSet>
----
<.> added, _before_ any of the ``<property>``s


Whether you choose to use layout file only or a mixture of layout file and annotations is a matter of taste.
Notice that the `.layout.xml` files has elements with the "unreferencedProperties", "unreferencedCollections" or "unreferencedActions" (and is considered invalid if these are missing).
As you might expect, these tags indicate where to render properties, collections or actions whose placement has not been specified explicitly.

[IMPORTANT]
====
This is an important principle of the _naked objects pattern_ ; the domain object should always be rendered in some way or another.
The presence of UI semantics (`@XxxLayout` annotations or the `.layout.xml` files) merely influence how that rendering is performed.
====


[#exercise-3-15-update-icon-for-pet-owner]
== Ex 3.15: Update icon for Pet Owner

As we've learnt in previous exercises, every domain object has a title which allows the end-user to distinguish one object from another.
Every domain object also has an icon, which helps distinguish one domain object _type_ from another.

The icon acts as the hyperlink from one domain object to another, for example from the home page to the `PetOwner` entity.
But choosing a good icon also improves the feedback cycle with your domain expert's; similar to personas, it helps to create a connection between the domain experts concept of the "thing" and its representation in the app's user interface.

In this exercise, we'll replace the icon for `PetOwner`.


=== Solution

[source,bash,subs="attributes+"]
----
git checkout tags/{tag-version}/03-15-change-pet-owner-icon-png
mvn clean install
mvn -pl webapp spring-boot:run -Dspring-boot.run.jvmArguments="-Dspring.profiles.active=dev"
----

=== Task

The icon for `PetOwner` is defined in the `PetOwner.png`.
All we need to do is replace it with some other icon.

* Download a `.png` icon (it will be auto-scaled, but 32, 64 or 80 pixels is a good size).
+
There are lots of good resources, for example https://icon8.com.
Remember to provide appropriate accreditation if required.

NOTE: the icon in the solution does indeed use a free icon from icons8.com, namely https://icons8.com/icons/set/person--icons8.

