You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by da...@apache.org on 2024/05/28 22:37:09 UTC
(causeway) branch 3.0.0 updated (51891a6f6e -> 970e42446f)
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a change to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
from 51891a6f6e updates stale references to 'jpa-SNAPSHOT' or 'jdo-SNAPSHOT'
new 90a25fe479 CAUSEWAY-2873: updates petclinic tutorial up to 2.5 incl.
new 0e34afd7f2 CAUSEWAY-2873: exercises 3-1, 3-2, 3-3
new d2f1d9934a CAUSEWAY-2873: 02-05, 03-04
new 3e4e686d82 CAUSEWAY-2873: adds 03-06
new 951ee53547 CAUSEWAY-2873: 03-06
new 2e42e9446f CAUSEWAY-2873: completes 03-08
new bcd2e8b4a4 CAUSEWAY-2873: 03-09
new 4ea10043b3 CAUSEWAY-2873: removes old exercise
new 807b2f62fa CAUSEWAY-2873: 03-13
new eb64a7f7cb CAUSEWAY-2873: attempts to improve styling of asciidoc plugin
new ad7ed85218 CAUSEWAY-2873: 03-14
new 5fc80f62dd CAUSEWAY-2873: 03-14 (2)
new 2bcf0f11fa CAUSEWAY-2873: 04-01
new 0fd7818724 CAUSEWAY-2873: 04-04
new 25f5400d35 CAUSEWAY-2873: 04-08
new f45b231503 CAUSEWAY-2873: 05-01
new 79ba585cc3 CAUSEWAY-2873: 05-02
new 8c6f8f5161 CAUSEWAY-2873: 06-01
new ca983b3915 CAUSEWAY-2873: 07-01
new aec600578b CAUSEWAY-2873: 09-01
new 90fba5e12b CAUSEWAY-2873: 08-02
new 3c3bb6b937 CAUSEWAY-2873: cleans up the _todo.adoc for petclinic
new 6f2c7f5f32 updates .sh to be executable
new fe0eedc705 CAUSEWAY-2873: updates toc for petclinic tutorial
new 962d8827e0 CAUSEWAY-2873: cleans up docs for generating website, maintenance of algolia config files
new 6d970c7479 CAUSEWAY-2783 : fixes docs re: causewaystuff-tooling-cli.jar file
new 015e2bea40 fixes invalid attribute name, was renamed
new fc2297847e removes reference to deleted 'tooling'
new 970e42446f CAUSEWAY-2873 : fixes toc for petclinic
The 29 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.asciidoctorconfig | 6 +
.gitignore | 3 +
.run/causeway-tooling-cli (index).run.xml | 22 -
.run/causeway-tooling-cli (overview).run.xml | 42 -
antora/.asciidoctor-styles/asciidoc-classic.css | 527 ++++++++
antora/.asciidoctor-styles/boot-flatly.css | 350 +++++
.../attachments/algolia-search/algolia-config.json | 26 +
.../ROOT/attachments/algolia-search/algolia.env | 2 +
.../ROOT/attachments/algolia-search/config.json | 40 -
.../modules/ROOT/pages/algolia-search.adoc | 40 +-
.../modules/ROOT/pages/cutting-a-release.adoc | 10 +-
.../ROOT/pages/post-release-successful.adoc | 43 +-
.../ROOT/pages/post-release-unsuccessful.adoc | 14 +-
.../ROOT/pages/release-process-prereqs.adoc | 103 +-
.../modules/ROOT/pages/updating-the-website.adoc | 60 +-
.../ROOT/partials/publish-and-index-website.adoc | 18 +-
.../modules/ROOT/partials/verify-process.adoc | 2 +-
.../components/docs/modules/ROOT/pages/about.adoc | 1 -
.../ROOT/partials/module-nav/framework.adoc | 1 -
.../images/03-06/home-page-default-columns.png | Bin 0 -> 21722 bytes
.../images/03-07/new-parameters-are-optional.png | Bin 0 -> 8602 bytes
.../petclinic/images/04-01/h2-console-prompt.png | Bin 0 -> 14517 bytes
.../04-01/pet-and-petowner-tables-created.png | Bin 0 -> 7712 bytes
.../petclinic/images/05-02/Visit-entity.png | Bin 0 -> 14304 bytes
...antuml-md5-5c0ff86629ce9ff23e5d52305493d5c6.svg | 1 +
...antuml-md5-7e47437805fde198d7791595949fcf17.png | Bin 0 -> 4464 bytes
.../tutorials/modules/petclinic/nav.adoc | 46 +-
.../petclinic/pages/010-getting-started.adoc | 40 +-
.../petclinic/pages/020-the-petclinic-domain.adoc | 363 +++++-
.../petclinic/pages/030-petowner-entity.adoc | 1342 ++++++++++++--------
.../modules/petclinic/pages/040-pet-entity.adoc | 1002 ++++++---------
.../modules/petclinic/pages/050-visit-entity.adoc | 490 +++++--
.../modules/petclinic/pages/060-unit-testing.adoc | 40 +-
.../modules/petclinic/pages/070-modularity.adoc | 155 +--
.../modules/petclinic/pages/080-view-models.adoc | 309 ++---
.../petclinic/pages/090-integration-testing.adoc | 119 +-
.../pages/{restful-api.txt => graphql-api.txt} | 2 +-
.../modules/petclinic/pages/todo.txt} | 22 +-
.../modules/petclinic/partials/domain.adoc | 15 +-
antora/playbooks/{site.yml => site.orig.yml} | 8 -
build-core.sh | 0
build-demo-app.sh | 0
build-site.sh | 0
build-smoketests.sh | 0
build-tooling.sh | 0
build.sh | 0
changed.sh | 0
core/adoc/modules/ROOT/partials/module-nav.adoc | 2 +-
dependency-graph.sh | 0
docker-run-demo.sh | 0
enhance.sh | 0
gitcommit.sh | 0
github-pr.sh | 0
gitrmb.sh | 0
preview.sh | 0
serve-timeline.sh | 0
sync-adoc.sh | 0
.../partials/fixture-scripts/api-and-usage.adoc | 1 +
58 files changed, 3313 insertions(+), 1954 deletions(-)
delete mode 100644 .run/causeway-tooling-cli (index).run.xml
delete mode 100644 .run/causeway-tooling-cli (overview).run.xml
create mode 100644 antora/.asciidoctor-styles/asciidoc-classic.css
create mode 100644 antora/.asciidoctor-styles/boot-flatly.css
create mode 100644 antora/components/comguide/modules/ROOT/attachments/algolia-search/algolia-config.json
create mode 100644 antora/components/comguide/modules/ROOT/attachments/algolia-search/algolia.env
delete mode 100644 antora/components/comguide/modules/ROOT/attachments/algolia-search/config.json
create mode 100644 antora/components/tutorials/modules/petclinic/images/03-06/home-page-default-columns.png
create mode 100644 antora/components/tutorials/modules/petclinic/images/03-07/new-parameters-are-optional.png
create mode 100644 antora/components/tutorials/modules/petclinic/images/04-01/h2-console-prompt.png
create mode 100644 antora/components/tutorials/modules/petclinic/images/04-01/pet-and-petowner-tables-created.png
create mode 100644 antora/components/tutorials/modules/petclinic/images/05-02/Visit-entity.png
create mode 100644 antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-5c0ff86629ce9ff23e5d52305493d5c6.svg
create mode 100644 antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-7e47437805fde198d7791595949fcf17.png
copy antora/components/tutorials/modules/petclinic/pages/{restful-api.txt => graphql-api.txt} (98%)
copy antora/components/{refguide-index/modules/applib/pages/index/jaxb/JavaSqlJaxbAdapters.adoc => tutorials/modules/petclinic/pages/todo.txt} (69%)
copy antora/playbooks/{site.yml => site.orig.yml} (98%)
mode change 100644 => 100755 build-core.sh
mode change 100644 => 100755 build-demo-app.sh
mode change 100644 => 100755 build-site.sh
mode change 100644 => 100755 build-smoketests.sh
mode change 100644 => 100755 build-tooling.sh
mode change 100644 => 100755 build.sh
mode change 100644 => 100755 changed.sh
mode change 100644 => 100755 dependency-graph.sh
mode change 100644 => 100755 docker-run-demo.sh
mode change 100644 => 100755 enhance.sh
mode change 100644 => 100755 gitcommit.sh
mode change 100644 => 100755 github-pr.sh
mode change 100644 => 100755 gitrmb.sh
mode change 100644 => 100755 preview.sh
mode change 100644 => 100755 serve-timeline.sh
mode change 100644 => 100755 sync-adoc.sh
(causeway) 29/29: CAUSEWAY-2873 : fixes toc for petclinic
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 970e42446f530fa9ac03853a1418b32baadba1b2
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Tue May 28 10:02:09 2024 +0100
CAUSEWAY-2873 : fixes toc for petclinic
---
antora/components/tutorials/modules/petclinic/nav.adoc | 4 +++-
.../modules/petclinic/pages/{_todo.adoc => graphql-api.txt} | 12 ++----------
.../modules/petclinic/pages/{_todo.adoc => todo.txt} | 0
core/adoc/modules/ROOT/partials/module-nav.adoc | 2 +-
4 files changed, 6 insertions(+), 12 deletions(-)
diff --git a/antora/components/tutorials/modules/petclinic/nav.adoc b/antora/components/tutorials/modules/petclinic/nav.adoc
index c653e31ec2..a3424f4901 100644
--- a/antora/components/tutorials/modules/petclinic/nav.adoc
+++ b/antora/components/tutorials/modules/petclinic/nav.adoc
@@ -11,7 +11,7 @@
** xref:020-the-petclinic-domain.adoc#exercise-2-1-refactor-simpleobject-to-petowner[image:hand.png[] *2.1*: Refactor SimpleObject to PetOwner]
* xref:030-petowner-entity.adoc[PetOwner entity]
-** xref:030-petowner-entity.adoc#exercise-3-1-add-petowners-knownAs-property[image:hand.png[] *3.1*: Add PetOwner's name property]
+** xref:030-petowner-entity.adoc#exercise-3-1-add-petowners-knownAs-property[image:hand.png[] *3.1*: Add PetOwner's knownAs property]
** xref:030-petowner-entity.adoc#exercise-3-2-define-PetOwners-title-imperatively[image:hand.png[] *3.2*: Define PetOwner's title imperatively]
** xref:030-petowner-entity.adoc#exercise-3-3-remaining-PetOwner-properties[image:hand.png[] *3.3*: Add remaining `PetOwner` properties]
** xref:030-petowner-entity.adoc#exercise-3-4-list-new-properties-of-PetOwner[image:hand.png[] *3.4*: List new properties of PetOwner]
@@ -62,8 +62,10 @@
// LATER: other sections could include
+// * xref:todo.txt[]
// * xref:further-business-logic-worked-examples.txt[]
// * xref:commands-and-auditing.txt[]
// * xref:restful-api.txt[]
+// * xref:graphql-api.txt[]
// * xref:i18n.txt[i18n]
// * xref:architecture-rules.txt[]
diff --git a/antora/components/tutorials/modules/petclinic/pages/_todo.adoc b/antora/components/tutorials/modules/petclinic/pages/graphql-api.txt
similarity index 69%
copy from antora/components/tutorials/modules/petclinic/pages/_todo.adoc
copy to antora/components/tutorials/modules/petclinic/pages/graphql-api.txt
index d2f920205b..f9bcc82dec 100644
--- a/antora/components/tutorials/modules/petclinic/pages/_todo.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/graphql-api.txt
@@ -1,14 +1,6 @@
-= Ideas for the future...
+= GraphQL API
: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 [...]
-
-* more user-friendly error message if hit duplicate (enter two pet owners with same name)
-
-* refactor addPet to be an inline-mixin.
-
-* add an disable and a hide action
-
-* PetOwner#daysSinceLastVisit could be made into a mixin
-** eg if these marketing analytics were the responsibility of some other module.
+// LATER
diff --git a/antora/components/tutorials/modules/petclinic/pages/_todo.adoc b/antora/components/tutorials/modules/petclinic/pages/todo.txt
similarity index 100%
rename from antora/components/tutorials/modules/petclinic/pages/_todo.adoc
rename to antora/components/tutorials/modules/petclinic/pages/todo.txt
diff --git a/core/adoc/modules/ROOT/partials/module-nav.adoc b/core/adoc/modules/ROOT/partials/module-nav.adoc
index ad29d84a83..a8b3aae31f 100644
--- a/core/adoc/modules/ROOT/partials/module-nav.adoc
+++ b/core/adoc/modules/ROOT/partials/module-nav.adoc
@@ -1,5 +1,5 @@
* xref:core:ROOT:about.adoc[Overview]
** xref:core:_overview:about.adoc[Maven modules]
-//** xref:core:ROOT:_todo.adoc[]
+//** xref:core:ROOT:todo.txt[]
(causeway) 25/29: CAUSEWAY-2873: cleans up docs for generating website, maintenance of algolia config files
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 962d8827e0e054cd7f9f1fea63ca142eecbcc92c
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Tue May 28 06:48:20 2024 +0100
CAUSEWAY-2873: cleans up docs for generating website, maintenance of algolia config files
---
.../attachments/algolia-search/algolia-config.json | 26 ++++++
.../ROOT/attachments/algolia-search/algolia.env | 2 +
.../ROOT/attachments/algolia-search/config.json | 40 --------
.../modules/ROOT/pages/algolia-search.adoc | 40 ++++++--
.../ROOT/pages/post-release-successful.adoc | 9 +-
.../ROOT/pages/release-process-prereqs.adoc | 103 ++++++++++++++++++++-
.../modules/ROOT/pages/updating-the-website.adoc | 60 +++---------
.../ROOT/partials/publish-and-index-website.adoc | 18 ++--
8 files changed, 181 insertions(+), 117 deletions(-)
diff --git a/antora/components/comguide/modules/ROOT/attachments/algolia-search/algolia-config.json b/antora/components/comguide/modules/ROOT/attachments/algolia-search/algolia-config.json
new file mode 100644
index 0000000000..a09a402d55
--- /dev/null
+++ b/antora/components/comguide/modules/ROOT/attachments/algolia-search/algolia-config.json
@@ -0,0 +1,26 @@
+{
+ "index_name": "causeway-apache-org",
+ "start_urls": [
+ "https://causeway.apache.org"
+ ],
+ "sitemap_urls": [
+ "https://causeway.apache.org/sitemap.xml"
+ ],
+ "stop_urls": [
+ "https://causeway[.]apache[.]org/(docs|comguide|conguide|setupguide|relnotes|refguide|userguide|core|security|extensions|valuetypes|vro|vw|pjdo|pjpa|testing|tutorials|incubator|regressiontests)/(2.0.0|2.0.0-RC2|2.0.0-RC1|2.0.0-M9|2.0.0-M8|2.0.0-M7|2.0.0-M6|2.0.0-M5)/.*",
+ "https://causeway[.]apache[.]org/versions/(1.17.0|1.16.2|1.16.1|1.16.0|1.15.1|1.15.0|1.14.0|1.13.2|1.13.2|1.13.1|1.13.0|1.12.2|1.12.1|1.12.0|1.11.1|1.11.0)/.*"
+ ],
+ "selectors": {
+ "lvl0": {
+ "selector": "//nav[@class='breadcrumbs']//li[last()]/a",
+ "type": "xpath",
+ "global": true,
+ "default_value": "Home"
+ },
+ "lvl1": ".doc h1"
+ ,"lvl2": ".doc h2"
+ ,"lvl3": ".doc h3"
+ ,"lvl4": ".doc h4"
+ ,"text": ".doc p, .doc td.content, .doc th.tableblock"
+ }
+}
diff --git a/antora/components/comguide/modules/ROOT/attachments/algolia-search/algolia.env b/antora/components/comguide/modules/ROOT/attachments/algolia-search/algolia.env
new file mode 100644
index 0000000000..77d299c47a
--- /dev/null
+++ b/antora/components/comguide/modules/ROOT/attachments/algolia-search/algolia.env
@@ -0,0 +1,2 @@
+APPLICATION_ID=5ISP5TFAEN
+API_KEY=xxxx
diff --git a/antora/components/comguide/modules/ROOT/attachments/algolia-search/config.json b/antora/components/comguide/modules/ROOT/attachments/algolia-search/config.json
deleted file mode 100644
index d213f94cab..0000000000
--- a/antora/components/comguide/modules/ROOT/attachments/algolia-search/config.json
+++ /dev/null
@@ -1,40 +0,0 @@
-{
- "index_name": "isis-apache-org",
- "start_urls": [
- "https://isis.apache.org"
- ],
- "sitemap_urls": [
- "https://isis.apache.org/sitemap.xml"
- ],
- "stop_urls": [
- "https://isis.apache.org/versions/1.17.0/documentation.html",
- "https://isis.apache.org/versions/1.16.2/documentation.html",
- "https://isis.apache.org/versions/1.16.1/documentation.html",
- "https://isis.apache.org/versions/1.16.0/documentation.html",
- "https://isis.apache.org/versions/1.15.1/documentation.html",
- "https://isis.apache.org/versions/1.15.0/documentation.html",
- "https://isis.apache.org/versions/1.14.0/documentation.html",
- "https://isis.apache.org/versions/1.13.2.1/documentation.html",
- "https://isis.apache.org/versions/1.13.2/documentation.html",
- "https://isis.apache.org/versions/1.13.1/documentation.html",
- "https://isis.apache.org/versions/1.13.0/documentation.html",
- "https://isis.apache.org/versions/1.12.2/documentation.html",
- "https://isis.apache.org/versions/1.12.1/documentation.html",
- "https://isis.apache.org/versions/1.12.0/documentation.html",
- "https://isis.apache.org/versions/1.11.1/documentation.html",
- "https://isis.apache.org/versions/1.11.0/documentation.html"
- ],
- "selectors": {
- "lvl0": {
- "selector": "//nav[@class='breadcrumbs']//li[last()]/a",
- "type": "xpath",
- "global": true,
- "default_value": "Home"
- },
- "lvl1": ".doc h1"
- ,"lvl2": ".doc h2"
- ,"lvl3": ".doc h3"
- ,"lvl4": ".doc h4"
- ,"text": ".doc p, .doc td.content, .doc th.tableblock"
- }
-}
\ No newline at end of file
diff --git a/antora/components/comguide/modules/ROOT/pages/algolia-search.adoc b/antora/components/comguide/modules/ROOT/pages/algolia-search.adoc
index 1430e9cc9e..14753adcf3 100644
--- a/antora/components/comguide/modules/ROOT/pages/algolia-search.adoc
+++ b/antora/components/comguide/modules/ROOT/pages/algolia-search.adoc
@@ -45,20 +45,46 @@ The "asf-site" branch is what is published, and the content of the site resides
+
CAUTION: the "admin API" should not be made public, as it allows the index to be modified or deleted.
+
+[#create-the-crawler-config]
== Create the Crawler config
We use Algolia's link:https://docsearch.algolia.com/docs/scraper/[DocSearch crawler] tool (also called the scraper) to index over the static html files to populate the index records.
-A config file describes how to process the files
+A config file describes how to process the files.
[source,json]
.algolia-config.json
----
-include::attachment$algolia-search/config.json[]
+include::attachment$algolia-search/algolia-config.json[]
+----
+
+The `stop_urls` property with any paths that should not be crawled.
+
+[NOTE]
+====
+Our policy is to only index the most recent version.
+This avoids lots of duplication in the index; previous versions of the page are easily accessible.
+====
+
+The config file reference for this file can be found link:https://docsearch.algolia.com/docs/config-file[here].
+
+The file itself resides in the `https://github.com/apache/causeway-site` repo (in the `asf-site` branch).
+
+
+[#create-the-algolia-env-file]
+== Create the algolia.env file
+
+The `algolia.env` file provides credentials to populate the search index maintained by the algolia service.
+
+[source,ini]
+.algolia.env
+----
+include::attachment$algolia-search/algolia.env[]
----
-The config file reference can be found link:https://docsearch.algolia.com/docs/config-file[here].
+The `API_KEY` is available in the `1password.com` vault shared between all Causeway committers.
-This has been checked into causeway-site as `./algolia-config.json`.
+The file itself resides in the `https://github.com/apache/causeway-site` repo (in the `asf-site` branch)
== Update the Antora UI bundle
@@ -80,8 +106,8 @@ There are other options for styling, see link:https://docsearch.algolia.com/docs
The remaining steps are routine and performed each time there is a change to the site:
-* xref:post-release-successful.adoc#generate-website[Generate website] and then
-* xref:post-release-successful.adoc#update-index-html-site-yml-generate[Index the site], and then
-* xref:post-release-successful.adoc#publish-website[Publish website].
+* xref:post-release-successful.adoc#generate-website[generate website]
+* xref:post-release-successful.adoc#publish-website[publish website].
+* xref:post-release-successful.adoc#update-the-algolia-search-index[update the algolia search index]
And you're done.
diff --git a/antora/components/comguide/modules/ROOT/pages/post-release-successful.adoc b/antora/components/comguide/modules/ROOT/pages/post-release-successful.adoc
index 9d15ed29ff..538531bdc8 100644
--- a/antora/components/comguide/modules/ROOT/pages/post-release-successful.adoc
+++ b/antora/components/comguide/modules/ROOT/pages/post-release-successful.adoc
@@ -360,14 +360,7 @@ mvnd clean install -T1C -DskipTests -Dgithub
popd
----
-* build module tooling
-+
-[source,bash,subs="attributes+"]
-----
-mvnd -D module-tooling -D skip.essential install -DskipTests
-----
-+
-NOTE: this now requires at least JDK 17 in order to build the `module-tooling` module that is used to generate some of the website.
+* if necessary, build the module tooling, as per xref:release-process-prereqs.adoc#doc-tooling[doc tooling prereqs] process.
* generate the website:
+
diff --git a/antora/components/comguide/modules/ROOT/pages/release-process-prereqs.adoc b/antora/components/comguide/modules/ROOT/pages/release-process-prereqs.adoc
index 74eabad489..564a041d96 100644
--- a/antora/components/comguide/modules/ROOT/pages/release-process-prereqs.adoc
+++ b/antora/components/comguide/modules/ROOT/pages/release-process-prereqs.adoc
@@ -5,12 +5,73 @@
:page-partial:
+This appendix describes the prerequisites for the xref:comguide:ROOT:cutting-a-release.adoc[release process].
+== Software
+Instructions here are for Linux.
+* jdk 21
++
+[source,bash]
+----
+sdk install java 21...
+----
++
+NOTE: for v2.x branch of Causeway we target Java 11 but we still build with the more recent version of Java.
+
+* mvn 3.9.7
++
+[source,bash]
+----
+sdk install maven 3.9.7
+----
+
+* groovy 3.x
++
+[source,bash]
+----
+sdk install groovy 3.0.21
+----
+
+* pandoc 2.x
++
+[source,bash]
+----
+sudo apt update
+sudo apt install pandoc
+----
+
+* dos2unix (if building on Windows)
+
+* Node LTS
++
+Antora recommend using `nvm` (Node version manager) to install Node:
+
+** install nvm
++
+[source,bash]
+----
+curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
+source ~/.bashrc
+----
+
+** install LTS version of node:
++
+[source,bash]
+----
+nvm install --lts
+----
+
+* Antora itself
++
+[source,bash]
+----
+npm i -g @antora/cli@^3.1.7 @antora/site-generator@^3.1.7 asciidoctor-kroki@0.17.0
+npm i -g serve@^13.0.2
+----
-This section (appendix) describes the prerequisites for the xref:comguide:ROOT:cutting-a-release.adoc[release process].
== Public/private key
@@ -80,3 +141,43 @@ Also, set up keyphrase for `gpg`; this avoids being prompted during release:
</activeProfiles>
----
+[#doc-tooling]
+== Doc Tooling
+
+NOTE: See also link:https://github.com/apache-causeway-committers/causewaystuff/blob/main/tooling/cli/README.adoc[causewaystuff] documentation.
+
+In the `apache-causeway-committers/causewaystuff` repo:
+
+* check out `main` branch
+
+* install/use Java 21
++
+For example:
++
+[source,java]
+----
+sdk use java 21...
+----
+
+* build the tooling
++
+[source,bash,subs="attributes+"]
+----
+mvn -D module-tooling install -Dskip.essential
+----
+
+In the regular `causeway` repo:
+
+* Create a symlink to the `causeway-tooling-cli` jar file
++
+[source,bash]
+----
+mkdir -p tooling/cli/target
+pushd tooling/cli/target
+ln -s ../../../../../apache-causeway-committers/causewaystuff/tooling/cli/target/causewaystuff-tooling-cli-spring-boot.jar causeway-tooling-cli.jar
+popd
+----
++
+NOTE: ADJUST PATH AS NECESSARY
+
+
diff --git a/antora/components/comguide/modules/ROOT/pages/updating-the-website.adoc b/antora/components/comguide/modules/ROOT/pages/updating-the-website.adoc
index fd08dd6364..07c0cab692 100644
--- a/antora/components/comguide/modules/ROOT/pages/updating-the-website.adoc
+++ b/antora/components/comguide/modules/ROOT/pages/updating-the-website.adoc
@@ -3,59 +3,21 @@
: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:
-As part of the release process we xref:post-release-successful.adoc#generate-website[generate] and publish the website.
-An (intentional) side-effect of this process is to create a branch for the release (that is, `{page-causewayrel}`).
-This branch can then be used for documentation updates.
-
-== Update docs
-
-Prereqs:
-
-* jdk 11
-* mvn 3.x
-* groovy 3.x
-* pandoc 2.x
-* dos2unix
-
-In the regular `causeway` repo:
+To page describes how to update the website _between_ releases.
-* Check out the branch:
-+
-[source,bash,subs="attributes+"]
-----
-git checkout {page-causewayrel}
-----
-
-* make documentation updates, and commit changes
-
-* make sure the tooling is built:
-+
-[source,bash,subs="attributes+"]
-----
-mvn -D module-tooling -D skip.essential install -DskipTests
-----
-
-* generate the website:
-+
-[source,bash,subs="attributes+"]
-----
-sh preview.sh
-----
-+
-[NOTE]
-====
-This now runs updates the config properties automatically (unless suppressed by a flag)
-
-[source,bash,subs="attributes+"]
-----
-mvn clean install -pl core/config
-----
-====
+As part of the release process we xref:post-release-successful.adoc#generate-website[generate] and publish the website.
+An (intentional) side-effect of this process is to create a branch for the release (that is, `{page-causewayprevv2}` or `{page-causewayprevv3}).
-This updated website is written to `antora/target/site`; we'll use the results in the xref:#publish-website[next section].
+If we need to make a change:
+* make the change on `master` as normal (so that the docs will be there for the next release)
+* cherry-pick the changes onto these documentation branches.
+Then publish as per the main release process:
+* xref:comguide:ROOT:post-release-successful.adoc#generate-the-site[generate the site]
+* xref:comguide:ROOT:post-release-successful.adoc#publish-website[publish the site].
+* xref:comguide:ROOT:post-release-successful.adoc#update-the-algolia-search-index[update the algolia search index]
-include::partial$publish-and-index-website.adoc[]
+Prereqs (software and doc tooling) are summarised in xref:comguide:ROOT:release-process-prereqs.adoc[].
diff --git a/antora/components/comguide/modules/ROOT/partials/publish-and-index-website.adoc b/antora/components/comguide/modules/ROOT/partials/publish-and-index-website.adoc
index fb806e82bb..2438a02670 100644
--- a/antora/components/comguide/modules/ROOT/partials/publish-and-index-website.adoc
+++ b/antora/components/comguide/modules/ROOT/partials/publish-and-index-website.adoc
@@ -60,20 +60,14 @@ git push origin asf-site
We use link:https://docsearch.algolia.com[Algolia] to build our search index.
-* If required, create a `algolia.env` file holding the `APP_ID` and the admin `API_KEY`, in the root of `causeway-site`:
-+
-[source,ini]
-.algolia.env
-----
-APPLICATION_ID=...
-API_KEY=...
-----
-+
-CAUTION: This file should not be checked into the repo, because the API_KEY allows the index to be modified or deleted.
+* If required, create a `algolia.env` file holding the `APP_ID` and the admin `API_KEY`, in the root of `causeway-site`; see xref:comguide:ROOT:algolia-search.adoc#create-the-algolia-env-file[appendix] for details.
-* If required, update the `algolia-config.json` file
+* If required, update the `algolia-config.json` file; see xref:comguide:ROOT:algolia-search.adoc#create-the-crawler-config[appendix] for details.
++
+In particular, update the `stop_urls` property with any paths that should not be crawled.
+
-For example, update the `stop_urls` property with any paths that should not be crawled.
+NOTE: Our policy is to only index the most recent version.
+This avoids lots of duplication in the index; previous versions of the page are easily accessible.
* Use the Algolia-provided link:https://hub.docker.com/r/algolia/docsearch-scraper[docker image] to crawl the web pages and create the search index:
+
(causeway) 02/29: CAUSEWAY-2873: exercises 3-1, 3-2, 3-3
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 0e34afd7f23cb8bb771c97cb1e66799ab90e129c
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Thu May 23 17:39:12 2024 +0100
CAUSEWAY-2873: exercises 3-1, 3-2, 3-3
---
.../petclinic/pages/030-petowner-entity.adoc | 236 +++++++++++----------
.../modules/petclinic/partials/domain.adoc | 8 +-
2 files changed, 125 insertions(+), 119 deletions(-)
diff --git a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
index 6b6e6b9bac..a7b0d399fb 100644
--- a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
@@ -5,12 +5,11 @@
In this set of exercises we'll just focus on the `PetOwner` entity.
-[#exercise-3-1-rename-petowners-name-property]
-== Ex 3.1: Rename PetOwner's name property
+[#exercise-3-1-rename-petowners-knownAs-property]
+== Ex 3.1: Add PetOwner's knownAs property
-In the domain we are working on, `PetOwner` has a `firstName` and a `lastName` property, not a single `name` property.
-
-In this exercise, we'll rename ``PetOwner``'s `name` property to be `lastName`, and change the fixture script that sets up data to something more realistic.
+In this exercise, we'll add a new property, `knownAs`, to capture the ``PetOwner``'s preferred name.
+We'll also update the fixture script that sets up data to something more realistic.
@@ -18,181 +17,184 @@ In this exercise, we'll rename ``PetOwner``'s `name` property to be `lastName`,
[source,bash]
----
-git checkout tags/03-01-renames-PetOwner-name-property
+git checkout tags/03-01-adds-PetOwner-knownAs-property
mvn clean install
mvn -pl spring-boot:run
----
-Remember you can use the menu:Prototyping[Fixture Scripts] menu to setup some example data.
+Use the menu:Prototyping[Fixture Scripts] menu to setup some example data.
=== Tasks
-Checkout the solution above and review the git history to see the changes that have already been made.
-These include:
-
-* property `PetOwner#name` -> `PetOwner#lastName` renamed
-* JPA mappings updated:
-** the corresponding JPQL named queries
-** the method names of `PetOwnerRepository`
+* add a `knownAs` property:
+
-This is a Spring Data repository, which uses a link:https://www.baeldung.com/spring-data-derived-queries[naming convention] to infer the queries
-
-** uniqueness constraint for `PetOwner`
-
-* the action method names of `PetOwners` domain service renamed
+[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
+
-This also requires updating the `menubars.layout.xml`, which references these action names.
+It's also possible to define UI semantics through the associated `.layout.xml` file; we'll look at that alternative in a later exercise.
-* updating the xref:refguide:applib:index/annotation/ActionLayout.adoc#associateWith[@ActionLayout] of the `updateName` and `delete` action methods in `PetOwner`
+* update `PetOwner_persona` to specify an `knownAs` for some of the owners:
+
-In the UI, the buttons for these actions are located close to the renamed "lastName" property
+[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;
+
+ ...
+}
+----
-* renames `@Name` meta-annotation to `@LastName`.
+* for the builder, use the `knownAs` value of the entity once created:
+
-Meta-annotations are a useful way of eliminating duplication where the same value type appears in multiple locations, for example as both an entity property and in action parameters.
+[source,java]
+.PetOwner_persona.java
+----
+@Override
+protected PetOwner buildResult(final ExecutionContext ec) {
-Build and run the application to make sure it still runs fine.
+ val petOwner = wrap(PetOwners).create(persona.name);
+ // ...
+ if (persona.knownAs != null) {
+ petOwner.setKnownAs(persona.knownAs);
+ }
+
+ // ...
+}
+----
-[#exercise-3-2-add-petowners-firstname-property]
-== Ex 3.2: Add PetOwner's firstName property
-Now that `PetOwner` has a `lastName` property, let's also add a `firstName` property.
-We'll also update our fixture script (which sets up ``PetOwner``s) so that it is more descriptive.
+
+[#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]
----
-git checkout tags/03-02-adds-PetOwner-firstName-property
+git checkout tags/03-02-define-PetOwner-title-imperatively
mvn clean install
mvn -pl spring-boot:run
----
=== Tasks
-* copy `@LastName` meta-annotation to create `@FirstName`:
-+
-[source,java]
-.FirstName.java
-----
-@Property(maxLength = FirstName.MAX_LEN, optionality = Optionality.OPTIONAL)
-@Parameter(maxLength = FirstName.MAX_LEN, optionality = Optionality.OPTIONAL)
-@ParameterLayout(named = "First Name")
-@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
-@Retention(RetentionPolicy.RUNTIME)
-public @interface FirstName {
-
- int MAX_LEN = 40;
-}
-----
-+
-Note that this property/parameter is optional.
-Its parameter name has also been updated.
-
+* locate the `name` property of `PetOwner`, and remove the `@Title` annotation
-* add a new (JPA nullable) property `firstName` to `PetOwner`:
-+
-[source,java]
-----
-@FirstName
-@Column(length = FirstName.MAX_LEN, nullable = true)
-@Getter @Setter @ToString.Include
-@PropertyLayout(fieldSetId = "name", sequence = "2")
-private String firstName;
-----
-
-* add a new factory method to accept a `firstName`, and refactor the existing one:
+* add a new `title()` method:
+
[source,java]
.PetOwner.java
----
-public static PetOwner withName(String name) {
- return withName(name, null);
-}
-
-public static PetOwner withName(String lastName, String firstName) {
- val simpleObject = new PetOwner();
- simpleObject.setLastName(lastName);
- simpleObject.setFirstName(firstName);
- return simpleObject;
+@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.
-* remove `@Title` annotation from `lastName` property, and add a `title()` method to derive from both properties:
-+
-[source,java]
-.PetOwner.java
-----
-public String title() {
- return getLastName() + (getFirstName() != null ? ", " + getFirstName() : "");
-}
-----
+[#exercise-3-3-remaining-PetOwner-properties]
+== Ex 3.3: Remaining `PetOwner` properties
-* Update the `PetOwner_persona` enum with more realistically last names (family names).
-+
-Learn more about fixture scripts xref:testing:fixtures:about.adoc[here].
+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
-[#exercise-3-3-modify-petowners-updatename-action]
-== Ex 3.3: Modify PetOwner's updateName action
-Although we've added a `firstName` property, currently it can't be edited.
-In this exercise we'll modify the `updateName` action to also allow the `firstName` to be changed.
+We'll make these changes in this exercise.
=== Solution
[source,bash]
----
-git checkout tags/03-03-modifies-PetOwner-updateName-action
+git checkout tags/03-03-remaining-PetOwner-properties
mvn clean install
mvn -pl spring-boot:run
----
=== Tasks
-* update `PetOwner#updateName` to also accept a new `firstName` parameter:
-+
-image::03-03/refactor-updateName.png[width=800px]
-+
-[source,java]
-.PetOwner.java
-----
-@Action(semantics = IDEMPOTENT, commandPublishing = Publishing.ENABLED, executionPublishing = Publishing.ENABLED)
-@ActionLayout(associateWith = "lastName", promptStyle = PromptStyle.INLINE)
-public PetOwner updateName(
- @LastName final String lastName,
- @FirstName String firstName) {
- setLastName(lastName);
- setFirstName(firstName);
- return this;
-}
-public String default0UpdateName() {
- return getLastName();
-}
-public String default1UpdateName() {
- return getFirstName();
-}
-----
+Rename the `lastCheckedIn` property to `lastVisit`:
-* add in a "default" supporting method for the new parameter.
+* 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.java
+.PetOwner_persona.java
----
-public String default1UpdateName() {
- return getFirstName();
-}
+final var numDaysAgo = fakeDataService.ints().between(100, 2); // <.>
+final var lastVisit = clockService.getClock().nowAsLocalDate().minusDays(numDaysAgo); // <.>
+petOwner.setLastVisit(lastVisit);
----
-+
-The "default" supporting methods are called when the action prompt is rendered, providing the default for the "Nth" parameter of the corresponding action.
+<.> 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
+
+Add the `telephoneNumber` property:
+
+* ...
+* ...
+
+Add the `emailAddress` property:
+
+* ...
+* ...
+
+TODO: ideas for future steps:
+- introduce a meta-annotation
+- introduce a custom type for these
diff --git a/antora/components/tutorials/modules/petclinic/partials/domain.adoc b/antora/components/tutorials/modules/petclinic/partials/domain.adoc
index 87641da592..e699f8cd23 100644
--- a/antora/components/tutorials/modules/petclinic/partials/domain.adoc
+++ b/antora/components/tutorials/modules/petclinic/partials/domain.adoc
@@ -28,11 +28,15 @@ package pets {
class PetOwner <<role>> {
+id
..
- #lastName
- #firstName
+ #name
+ #knownAs
..
-phoneNumber
-emailAddress
+ ..
+ -lastVisit
+ ..
+ -notes
}
}
(causeway) 05/29: CAUSEWAY-2873: 03-06
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 951ee53547ba06f47c0358c6e75c2977c937f7a7
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sat May 25 07:37:23 2024 +0100
CAUSEWAY-2873: 03-06
---
.../images/03-06/home-page-default-columns.png | Bin 0 -> 21722 bytes
...ag-plantuml-md5-41d7321e937c89d99e72b59cadcbe005.png | Bin 0 -> 37003 bytes
...ag-plantuml-md5-5c0ff86629ce9ff23e5d52305493d5c6.svg | 1 +
...ag-plantuml-md5-7e47437805fde198d7791595949fcf17.png | Bin 0 -> 5096 bytes
...ag-plantuml-md5-bc5fc3dd9c919d1677af89bec5416f29.svg | 1 +
.../modules/petclinic/pages/030-petowner-entity.adoc | 7 ++++++-
6 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/antora/components/tutorials/modules/petclinic/images/03-06/home-page-default-columns.png b/antora/components/tutorials/modules/petclinic/images/03-06/home-page-default-columns.png
new file mode 100644
index 0000000000..dc0d1f0aa2
Binary files /dev/null and b/antora/components/tutorials/modules/petclinic/images/03-06/home-page-default-columns.png differ
diff --git a/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-41d7321e937c89d99e72b59cadcbe005.png b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-41d7321e937c89d99e72b59cadcbe005.png
new file mode 100644
index 0000000000..9b948f4bae
Binary files /dev/null and b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-41d7321e937c89d99e72b59cadcbe005.png differ
diff --git a/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-5c0ff86629ce9ff23e5d52305493d5c6.svg b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-5c0ff86629ce9ff23e5d52305493d5c6.svg
new file mode 100644
index 0000000000..6f3b8ba884
--- /dev/null
+++ b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-5c0ff86629ce9ff23e5d52305493d5c6.svg
@@ -0,0 +1 @@
+<?xml version='1.0' encoding='US-ASCII' standalone='no'?><svg contentStyleType='text/css' height='161px' preserveAspectRatio='none' style='width:141px;height:161px;background:#FFFFFF;' version='1.1' viewBox='0 0 141 161' width='141px' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns='http://www.w3.org/2000/svg' zoomAndPan='magnify'><defs/><g><g id='elem_Owner'><rect codeLine='4' fill='#F1F1F1' height='140.7144' id='Owner' rx='2.5' ry='2.5' style='stroke:#181818;stroke-width:0.5;' width=' [...]
\ No newline at end of file
diff --git a/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-7e47437805fde198d7791595949fcf17.png b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-7e47437805fde198d7791595949fcf17.png
new file mode 100644
index 0000000000..12035b77b0
Binary files /dev/null and b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-7e47437805fde198d7791595949fcf17.png differ
diff --git a/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-bc5fc3dd9c919d1677af89bec5416f29.svg b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-bc5fc3dd9c919d1677af89bec5416f29.svg
new file mode 100644
index 0000000000..a9963efc83
--- /dev/null
+++ b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-bc5fc3dd9c919d1677af89bec5416f29.svg
@@ -0,0 +1 @@
+<?xml version='1.0' encoding='US-ASCII' standalone='no'?><svg contentStyleType='text/css' height='441px' preserveAspectRatio='none' style='width:520px;height:441px;background:#FFFFFF;' version='1.1' viewBox='0 0 520 441' width='520px' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns='http://www.w3.org/2000/svg' zoomAndPan='magnify'><defs/><g><g id='cluster_pets'><path d='M8.5,6 L38.5,6 A3.75,3.75 0 0 1 41,8.5 L48,28.9429 L282.5,28.9429 A2.5,2.5 0 0 1 285,31.4429 L285,431.5 A2.5,2.5 0 0 1 [...]
\ No newline at end of file
diff --git a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
index 7e3eec8025..4d46d3bc39 100644
--- a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
@@ -12,7 +12,6 @@ In this exercise, we'll add a new property, `knownAs`, to capture the ``PetOwner
We'll also update the fixture script that sets up data to something more realistic.
-
=== Solution
[source,bash]
@@ -384,8 +383,14 @@ The positioning of the collection is also specified in the corresponding layout
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`:
+
+Instead
+
[source,txt]
.HomePageViewModel#petOwners.columnOrder.txt
----
(causeway) 24/29: CAUSEWAY-2873: updates toc for petclinic tutorial
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit fe0eedc705d53add2c28d8321e090e5a754e691d
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Mon May 27 19:13:34 2024 +0100
CAUSEWAY-2873: updates toc for petclinic tutorial
---
.asciidoctorconfig | 6 +--
.../tutorials/modules/petclinic/nav.adoc | 44 +++++++++++-----------
.../petclinic/pages/030-petowner-entity.adoc | 6 +--
3 files changed, 29 insertions(+), 27 deletions(-)
diff --git a/.asciidoctorconfig b/.asciidoctorconfig
index 68dced8e96..a13e65f25b 100644
--- a/.asciidoctorconfig
+++ b/.asciidoctorconfig
@@ -10,9 +10,9 @@
// see discussion here https://github.com/asciidoctor/asciidoctor-intellij-plugin/issues/548
:kroki-default-options: inline
-:linkcss:
-:stylesdir: {asciidoctorconfigdir}/antora/.asciidoctor-styles
-:stylesheet: asciidoc-classic.css
+// :linkcss:
+// :stylesdir: {asciidoctorconfigdir}/antora/.asciidoctor-styles
+// :stylesheet: asciidoc-classic.css
// :stylesdir: https://raw.githubusercontent.com/darshandsoni/asciidoctor-skins/gh-pages/css
// :stylesheet: asciidoc-classic.css
diff --git a/antora/components/tutorials/modules/petclinic/nav.adoc b/antora/components/tutorials/modules/petclinic/nav.adoc
index 03f11a3fb9..c653e31ec2 100644
--- a/antora/components/tutorials/modules/petclinic/nav.adoc
+++ b/antora/components/tutorials/modules/petclinic/nav.adoc
@@ -11,35 +11,38 @@
** xref:020-the-petclinic-domain.adoc#exercise-2-1-refactor-simpleobject-to-petowner[image:hand.png[] *2.1*: Refactor SimpleObject to PetOwner]
* xref:030-petowner-entity.adoc[PetOwner entity]
-** xref:030-petowner-entity.adoc#exercise-3-1-rename-petowners-name-property[image:hand.png[] *3.1*: Rename PetOwner's name property]
-** xref:030-petowner-entity.adoc#exercise-3-2-add-petowners-firstname-property[image:hand.png[] *3.2*: Add PetOwner's firstName property]
-** xref:030-petowner-entity.adoc#exercise-3-3-modify-petowners-updatename-action[image:hand.png[] *3.3*: Modify PetOwner's updateName action]
-** xref:030-petowner-entity.adoc#exercise-3-4-modify-the-menu-action-to-create-petowners[image:hand.png[] *3.4*: Modify the menu action to create PetOwners]
+** xref:030-petowner-entity.adoc#exercise-3-1-add-petowners-knownAs-property[image:hand.png[] *3.1*: Add PetOwner's name property]
+** xref:030-petowner-entity.adoc#exercise-3-2-define-PetOwners-title-imperatively[image:hand.png[] *3.2*: Define PetOwner's title imperatively]
+** xref:030-petowner-entity.adoc#exercise-3-3-remaining-PetOwner-properties[image:hand.png[] *3.3*: Add remaining `PetOwner` properties]
+** xref:030-petowner-entity.adoc#exercise-3-4-list-new-properties-of-PetOwner[image:hand.png[] *3.4*: List new properties of PetOwner]
** xref:030-petowner-entity.adoc#exercise-3-5-initial-fixture-script[image:hand.png[] *3.5*: Initial Fixture Script]
-** xref:030-petowner-entity.adoc#exercise-3-6-prompt-styles[image:hand.png[] *3.6*: Prompt styles]
-** xref:030-petowner-entity.adoc#exercise-3-7-derived-name-property[image:hand.png[] *3.7*: Derived name property]
-** xref:030-petowner-entity.adoc#exercise-3-8-add-other-properties-for-petowner[image:hand.png[] *3.8*: Add other properties for PetOwner]
-** xref:030-petowner-entity.adoc#exercise-3-9-validation[image:hand.png[] *3.9*: Validation]
-** xref:030-petowner-entity.adoc#exercise-3-10-field-layout[image:hand.png[] *3.10*: Field layout]
-** xref:030-petowner-entity.adoc#exercise-3-11-column-orders[image:hand.png[] *3.11*: Column Orders]
+** xref:030-petowner-entity.adoc#exercise-3-6-update-home-page-to-show-PetOwners[image:hand.png[] *3.6*: Update Home Page to show PetOwners]
+** xref:030-petowner-entity.adoc#exercise-3-7-modify-the-menu-action-to-create-petowners[image:hand.png[] *3.7*: Modify the menu action to create PetOwners]
+** xref:030-petowner-entity.adoc#exercise-3-8-prompt-styles[image:hand.png[] *3.8*: Prompt styles]
+** xref:030-petowner-entity.adoc#exercise-3-9-derived-days-since-last-visit-property[image:hand.png[] *3.9*: Derived 'days since last visit' property]
+** xref:030-petowner-entity.adoc#exercise-3-10-use-meta-annotations-to-reduce-duplication[image:hand.png[] *3.10*: Use meta-annotations to reduce duplication]
+** xref:030-petowner-entity.adoc#exercise-3-11-validation[image:hand.png[] *3.11*: Validation]
+** xref:030-petowner-entity.adoc#exercise-3-12-more-validation[image:hand.png[] *3.12*: More validation]
+** xref:030-petowner-entity.adoc#exercise-3-13-scalar-custom-value-types[image:hand.png[] *3.13*: Scalar custom value types]
+** xref:030-petowner-entity.adoc#exercise-3-14-use-layout-xml-file-for-ui-semantics[image:hand.png[] *3.14*: Use layout xml file for UI semantics]
+** xref:030-petowner-entity.adoc#exercise-3-15-update-icon-for-pet-owner[image:hand.png[] *3.15*: Update icon for Pet Owner]
* xref:040-pet-entity.adoc[Pet entity]
** xref:040-pet-entity.adoc#exercise-4-1-pet-entitys-key-properties[image:hand.png[] *4.1*: Pet entity's key properties]
-** xref:040-pet-entity.adoc#exercise-4-2-add-petrepository[image:hand.png[] *4.2*: Add PetRepository]
+** xref:040-pet-entity.adoc#exercise-4-2-add-pet-ui-and-layout-semantics[image:hand.png[] *4.2*: Add Pet's UI and layout semantics]
** xref:040-pet-entity.adoc#exercise-4-3-add-petowners-collection-of-pets[image:hand.png[] *4.3*: Add PetOwner's collection of Pets]
-** xref:040-pet-entity.adoc#exercise-4-4-add-pets-remaining-properties[image:hand.png[] *4.4*: Add Pet's remaining properties]
-** xref:040-pet-entity.adoc#exercise-4-5-digression-clean-up-casing-of-database-schema[image:hand.png[] *4.5*: Digression: clean-up casing of database schema]
-** xref:040-pet-entity.adoc#exercise-4-6-add-petowner-action-to-add-pets[image:hand.png[] *4.6*: Add PetOwner action to add Pets]
-** xref:040-pet-entity.adoc#exercise-4-7-add-pets-ui-customisation[image:hand.png[] *4.7*: Add Pet's UI customisation]
-** xref:040-pet-entity.adoc#exercise-4-8-update-fixture-script-using-pet-personas[image:hand.png[] *4.8*: Update fixture script using Pet personas]
-** xref:040-pet-entity.adoc#exercise-4-9-add-petowner-action-to-delete-a-pet[image:hand.png[] *4.9*: Add PetOwner action to delete a Pet]
-** xref:040-pet-entity.adoc#exercise-4-10-cleanup[image:hand.png[] *4.10*: Cleanup]
+** xref:040-pet-entity.adoc#exercise-4-4-add-actions-to-add-or-remove-pets[image:hand.png[] *4.4*: Add actions to add or remove Pets]
+** xref:040-pet-entity.adoc#exercise-4-5-extend-the-fixture-data-to-add-in-Pets[image:hand.png[] *4.5*: Extend the fixture data to add in Pets]
+** xref:040-pet-entity.adoc#exercise-4-6-add-action-validation[image:hand.png[] *4.6*: Unique pet names (action validation)]
+** xref:040-pet-entity.adoc#exercise-4-7-add-pets-remaining-properties[image:hand.png[] *4.7*: Add Pet's remaining properties]
+** xref:040-pet-entity.adoc#exercise-4-8-dynamic-icons-for-pet[image:hand.png[] *4.8*: Add dynamic icons for Pet]
* xref:050-visit-entity.adoc[Visit module and entity]
** xref:050-visit-entity.adoc#exercise-5-1-the-visits-module[image:hand.png[] *5.1*: The visits module]
-** xref:050-visit-entity.adoc#exercise-5-2-visit-entitys-key-properties[image:hand.png[] *5.2*: Visit entity's key properties]
-** xref:050-visit-entity.adoc#exercise-5-3-book-visit-action[image:hand.png[] *5.3*: "Book Visit" action]
+** xref:050-visit-entity.adoc#exercise-5-2-visit-module-dependencies[image:hand.png[] *5.2*: Visit Module Dependencies]
+** xref:050-visit-entity.adoc#exercise-5-3-visit-entitys-key-properties[image:hand.png[] *5.3*: Visit entity's key properties]
+** xref:050-visit-entity.adoc#exercise-5-4-book-visit-action[image:hand.png[] *5.4*: "Book Visit" action]
* xref:060-unit-testing.adoc[Unit Testing]
@@ -52,7 +55,6 @@
* xref:080-view-models.adoc[View Models]
** xref:080-view-models.adoc#exercise-8-1-extend-the-home-page[image:hand.png[] *8.1*: Extend the Home Page.]
** xref:080-view-models.adoc#exercise-8-2-add-a-convenience-action[image:hand.png[] *8.2*: Add a convenience action]
-** xref:080-view-models.adoc#exercise-8-3-using-a-view-model-as-a-projection-of-an-entity[image:hand.png[] *8.3*: Using a view model as a projection of an entity]
* xref:090-integration-testing.adoc[Integration Testing]
diff --git a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
index 73cc8c874d..93bbc8a530 100644
--- a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
@@ -5,7 +5,7 @@
In this set of exercises we'll just focus on the `PetOwner` entity.
-[#exercise-3-1-rename-petowners-knownAs-property]
+[#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.
@@ -142,7 +142,7 @@ public String title() {
[#exercise-3-3-remaining-PetOwner-properties]
-== Ex 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:
@@ -226,7 +226,7 @@ For example, with IntelliJ you can use "Reload Changed Classes" menu item.
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
+== 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.
(causeway) 27/29: fixes invalid attribute name, was renamed
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 015e2bea40b68218bf8d5657b5b67699424a4fab
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Tue May 28 09:19:49 2024 +0100
fixes invalid attribute name, was renamed
---
.../modules/ROOT/pages/cutting-a-release.adoc | 10 +++----
.../ROOT/pages/post-release-successful.adoc | 34 +++++++++++-----------
.../ROOT/pages/post-release-unsuccessful.adoc | 14 ++++-----
.../modules/ROOT/partials/verify-process.adoc | 2 +-
4 files changed, 30 insertions(+), 30 deletions(-)
diff --git a/antora/components/comguide/modules/ROOT/pages/cutting-a-release.adoc b/antora/components/comguide/modules/ROOT/pages/cutting-a-release.adoc
index 8390d04ae4..5eaaa6104b 100644
--- a/antora/components/comguide/modules/ROOT/pages/cutting-a-release.adoc
+++ b/antora/components/comguide/modules/ROOT/pages/cutting-a-release.adoc
@@ -193,7 +193,7 @@ For example:
----
export CAUSEWAYJIRA=CAUSEWAY-9999 # <.>
export CAUSEWAYTMP=/c/tmp # <.>
-export CAUSEWAYREL={page-causewayrel} # <.>
+export CAUSEWAYREL={page-causewayrelv3} # <.>
export CAUSEWAYRC=RC1 # <.>
export CAUSEWAYBRANCH=release-$CAUSEWAYREL-$CAUSEWAYRC
export CAUSEWAYART=causeway
@@ -208,7 +208,7 @@ env | grep CAUSEWAY | sort
[IMPORTANT]
====
-The branch name is intentionally *not* the same as the eventual tag names (eg `causeway-{page-causewayrel}`).
+The branch name is intentionally *not* the same as the eventual tag names (eg `causeway-{page-causewayrelv3}`).
====
@@ -842,7 +842,7 @@ Use the following subject, eg:
[source,subs="attributes+"]
----
-[VOTE] Apache Causeway Core release {page-causewayrel} RC1
+[VOTE] Apache Causeway Core release {page-causewayrelv3} RC1
----
And use the following body:
@@ -854,7 +854,7 @@ I've just cut a new release of the Apache Causeway Framework.
The source code zip artifact has been uploaded to a staging repository on
https://repository.apache.org, along with its corresponding .asc signature.
-In the source code repo the code has been tagged as causeway-{page-causewayrel}-RC1;
+In the source code repo the code has been tagged as causeway-{page-causewayrelv3}-RC1;
see https://github.com/apache/causeway/tags
To verify the source code itself, you can use the following commands
@@ -880,7 +880,7 @@ The vote will be open for a minimum of 72 hours.
Remember to update:
-* the version number (`{page-causewayrel}` or whatever)
+* the version number (`{page-causewayrelv3}` or whatever)
* the release candidate number (`RC1` or whatever)
* the `NEXUSREPONUM` to the repository id as provided by Nexus earlier (`11xx` or whatever)
diff --git a/antora/components/comguide/modules/ROOT/pages/post-release-successful.adoc b/antora/components/comguide/modules/ROOT/pages/post-release-successful.adoc
index 538531bdc8..cef51cdd1f 100644
--- a/antora/components/comguide/modules/ROOT/pages/post-release-successful.adoc
+++ b/antora/components/comguide/modules/ROOT/pages/post-release-successful.adoc
@@ -22,7 +22,7 @@ Post the results to the `dev@causeway.a.o` mailing list:
[source,subs="attributes+"]
----
-[RESULT] [VOTE] Apache Causeway Core release {page-causewayrel}
+[RESULT] [VOTE] Apache Causeway Core release {page-causewayrelv3}
----
using the body (alter last line as appropriate):
@@ -60,7 +60,7 @@ As we did for the cutting of the release, we set environment variables to parame
----
export CAUSEWAYJIRA=CAUSEWAY-9999 # <.>
export CAUSEWAYTMP=/c/tmp # <.>
-export CAUSEWAYREL={page-causewayrel} # <.>
+export CAUSEWAYREL={page-causewayrelv3} # <.>
export CAUSEWAYRC=RC1 # <.>
export CAUSEWAYBRANCH=release-$CAUSEWAYREL-$CAUSEWAYRC
export CAUSEWAYART=causeway
@@ -234,7 +234,7 @@ done
[source,bash,subs="attributes+"]
----
-sh upd.sh {page-causewayrel}
+sh upd.sh {page-causewayrelv3}
----
The script downloads the artifacts from the Nexus release repository and adds the artifacts to subversion.
@@ -382,7 +382,7 @@ git commit -m "$CAUSEWAYJIRA: updates docs on new release branch"
+
[source,bash,subs="attributes+"]
----
-git push origin {page-causewayrel}
+git push origin {page-causewayrelv3}
----
@@ -390,7 +390,7 @@ git push origin {page-causewayrel}
=== Generate the site
To build the site , we need to update the `site.yml`.
-This is done in the release branch (`$CAUSEWAYBRANCH`), _not_ in the documentation branch for the current release ({page-causewayrel}).
+This is done in the release branch (`$CAUSEWAYBRANCH`), _not_ in the documentation branch for the current release ({page-causewayrelv3}).
* Switch to the release branch:
+
@@ -400,11 +400,11 @@ git checkout $CAUSEWAYBRANCH #<.>
----
<.> will be before the documentation branch of the current release
-* Update with the documentation branch for the current release ({page-causewayrel})
+* Update with the documentation branch for the current release ({page-causewayrelv3})
+
[source,bash,subs="attributes+"]
----
-git merge {page-causewayrel} --ff-only #<.>
+git merge {page-causewayrelv3} --ff-only #<.>
----
<.> move forward to incorporate any changes made thus far on the documentation branch
@@ -443,11 +443,11 @@ content:
sources:
- url: .
start_path: antora/components/docs # docs
- branches: [{page-causewayrel}]
+ branches: [{page-causewayrelv3}]
# ...
- url: .
start_path: viewers/wicket/adoc # vw
- branches: [{page-causewayrel}]
+ branches: [{page-causewayrelv3}]
----
+
NOTE: Previously we built a site with the current version and all previous versions.
@@ -530,10 +530,10 @@ In the `causeway` repo:
RC=RC1 <.>
git checkout master
git pull
-git merge release-{page-causewayrel}-$RC
+git merge release-{page-causewayrelv3}-$RC
git push origin master
-git branch -d release-{page-causewayrel}-$RC
-git push origin release-{page-causewayrel}-$RC --delete
+git branch -d release-{page-causewayrelv3}-$RC
+git push origin release-{page-causewayrelv3}-$RC --delete
----
<.> adjust RC number as required
@@ -552,7 +552,7 @@ jobs:
build:
env:
- BASELINE: {page-causewayrel}
+ BASELINE: {page-causewayrelv3}
----
* in `ci-build-site-no-push.yml`:
@@ -564,7 +564,7 @@ jobs:
build:
env:
- BASELINE: {page-causewayrel}
+ BASELINE: {page-causewayrelv3}
----
* commit and push:
@@ -599,7 +599,7 @@ jobs:
runs-on: ubuntu-latest
env:
# to be shared among all steps of this job
- BASELINE_MAIN: {page-causewayrel}
+ BASELINE_MAIN: {page-causewayrelv3}
BASELINE_EDGE: 3.0.0-M1
----
+
@@ -656,14 +656,14 @@ For example, for a release of Apache Causeway Core, use the following subject:
[source,subs="attributes+"]
----
-[ANN] Apache Causeway version {page-causewayrel} Released
+[ANN] Apache Causeway version {page-causewayrelv3} Released
----
And use the following body (summarizing the main points as required):
[source,subs="attributes+"]
----
-The Apache Causeway team is pleased to announce the release of Apache Causeway {page-causewayrel}.
+The Apache Causeway team is pleased to announce the release of Apache Causeway {page-causewayrelv3}.
New features in this release include:
diff --git a/antora/components/comguide/modules/ROOT/pages/post-release-unsuccessful.adoc b/antora/components/comguide/modules/ROOT/pages/post-release-unsuccessful.adoc
index d7d733aac2..671bb45beb 100644
--- a/antora/components/comguide/modules/ROOT/pages/post-release-unsuccessful.adoc
+++ b/antora/components/comguide/modules/ROOT/pages/post-release-unsuccessful.adoc
@@ -22,7 +22,7 @@ For example, use the following subject for a vote on Apache Causeway Core:
[source,bash,subs="attributes+"]
----
-[RESULT] [VOTE] Apache Causeway Core release {page-causewayrel}
+[RESULT] [VOTE] Apache Causeway Core release {page-causewayrelv3}
----
using the body (alter last line as appropriate):
@@ -49,10 +49,10 @@ Branches and tags need to be removed for three repos:
+
[source,bash,subs="attributes+"]
----
-git push --delete origin release-{page-causewayrel}-RC1 # <.>
-git push --delete origin causeway-{page-causewayrel}-RC1 # <.>
-git tag -d causeway-{page-causewayrel} # <.>
-git tag -d causeway-{page-causewayrel}-RC1
+git push --delete origin release-{page-causewayrelv3}-RC1 # <.>
+git push --delete origin causeway-{page-causewayrelv3}-RC1 # <.>
+git tag -d causeway-{page-causewayrelv3} # <.>
+git tag -d causeway-{page-causewayrelv3}-RC1
----
<.> deletes the remote branch (update `RCn` as necessary)
<.> deletes the remote origin server's tag (update `RCn` as necessary)
@@ -62,14 +62,14 @@ git tag -d causeway-{page-causewayrel}-RC1
+
[source,bash,subs="attributes+"]
----
-git push --delete origin release-{page-causewayrel}-RC1 # <.>
+git push --delete origin release-{page-causewayrelv3}-RC1 # <.>
----
* For simpleapp starter app, similarly:
+
[source,bash,subs="attributes+"]
----
-git push --delete origin release-{page-causewayrel}-RC1 # <.>
+git push --delete origin release-{page-causewayrelv3}-RC1 # <.>
----
diff --git a/antora/components/comguide/modules/ROOT/partials/verify-process.adoc b/antora/components/comguide/modules/ROOT/partials/verify-process.adoc
index a1afe2ba8e..8e4b65d9d1 100644
--- a/antora/components/comguide/modules/ROOT/partials/verify-process.adoc
+++ b/antora/components/comguide/modules/ROOT/partials/verify-process.adoc
@@ -1,4 +1,4 @@
-VERSION={page-causewayrel}
+VERSION={page-causewayrelv3}
RC=RC1
NEXUSREPONUM=11xx
(causeway) 20/29: CAUSEWAY-2873: 09-01
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit aec600578b6b896829079d10f7f1633041738684
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sun May 26 23:16:56 2024 +0100
CAUSEWAY-2873: 09-01
but skipped 08 accidentally
---
.../petclinic/pages/090-integration-testing.adoc | 119 +++++++++------------
1 file changed, 51 insertions(+), 68 deletions(-)
diff --git a/antora/components/tutorials/modules/petclinic/pages/090-integration-testing.adoc b/antora/components/tutorials/modules/petclinic/pages/090-integration-testing.adoc
index 425257ee68..b79e5e6e01 100644
--- a/antora/components/tutorials/modules/petclinic/pages/090-integration-testing.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/090-integration-testing.adoc
@@ -4,8 +4,9 @@
In an earlier section of this tutorial we looked at unit testing, but integration tests are at least as important, probably more so, as they exercise the entire application from an end-users perspective, rather than an individual part.
-Integration tests are _not_ written using Selenium or similar, so avoid the fragility and maintenance effort that such tests often entail.
+We don't write integration tests using Selenium or similar, and so avoid the fragility and maintenance effort that such tests often entail.
Instead, the framework provides the xref:refguide:applib:index/services/wrapper/WrapperFactory.adoc[WrapperFactory] domain service which simulates the user interface in a type-safe way.
+Another term sometimes used is _subcutaneous_ testing; we execute the test "under the skin".
[#exercise-9-1-testing-bookvisit-using-an-integtest]
@@ -24,29 +25,35 @@ mvn clean package jetty:run
=== Tasks
-* in the `pom.xml` of the visits module, add the following dependency:
+* in the `pom.xml` of the visits module, add the following dependencies:
+
[source,xml]
.module-visits/pom.xml
----
<dependency>
- <groupId>org.apache.causeway.mavendeps</groupId>
- <artifactId>causeway-mavendeps-integtests</artifactId>
- <type>pom</type>
+ <groupId>org.apache.causeway.testing</groupId>
+ <artifactId>causeway-testing-integtestsupport-applib</artifactId>
+ <scope>test</scope>
+</dependency>
+
+<dependency>
+ <groupId>org.apache.causeway.persistence</groupId>
+ <artifactId>causeway-persistence-jpa-eclipselink</artifactId>
<scope>test</scope>
</dependency>
----
-* add an abstract class `VisitsModuleIntegTestAbstract` for the `visits` module, for other integ tests to subclass:
+
+* add an abstract class `VisitModuleIntegTestAbstract` for the `visits` module, for other integ tests to subclass:
+
[source,java]
-.VisitsModuleIntegTestAbstract.java
+.VisitModuleIntegTestAbstract.java
----
@SpringBootTest(
- classes = VisitsModuleIntegTestAbstract.TestApp.class
+ classes = VisitModuleIntegTestAbstract.TestApp.class
)
@ActiveProfiles("test")
-public abstract class VisitsModuleIntegTestAbstract
+public abstract class VisitModuleIntegTestAbstract
extends CausewayIntegrationTestAbstractWithFixtures {
@SpringBootConfiguration
@@ -58,7 +65,7 @@ public abstract class VisitsModuleIntegTestAbstract
CausewayModulePersistenceJpaEclipselink.class,
CausewayModuleTestingFixturesApplib.class,
- VisitsModule.class
+ VisitModule.class // <.>
})
@PropertySources({
@PropertySource(CausewayPresets.H2InMemory_withUniqueSchema),
@@ -68,16 +75,17 @@ public abstract class VisitsModuleIntegTestAbstract
}
}
----
+<.> Most of this class is boilerplate, but it does reference the module under test.
-* also update the `application-test.yml` file for the `visits` module, to ensure that the database schemas for both modules are created:
+* also update the `application-test.yml` file for the `visit` module, to ensure that the database schemas for both modules are created:
+
[source,yaml]
-.module-visits/src/test/resources/application-test.yml
+.module-visit/src/test/resources/application-test.yml
----
causeway:
persistence:
schema:
- auto-create-schemas: pets,visits
+ auto-create-schemas: petowner,visit
----
* add a class `Bootstrap_IntegTest` integration test, inheriting from the `VisitsModuleIntegTestAbstract:
@@ -85,7 +93,7 @@ causeway:
[source,java]
.Bootstrap_IntegTest.java
----
-public class Bootstrap_IntegTest extends VisitsModuleIntegTestAbstract {
+public class Bootstrap_IntegTest extends VisitModuleIntegTestAbstract {
@Test
public void checks_can_bootstrap() {}
@@ -97,42 +105,46 @@ Make sure this test runs and passes in both the IDE and using "mvn clean install
Now we can write our actual test:
-* Now add a class `Pet_bookVisit_IntegTest` integration test, also inheriting from the `VisitsModuleIntegTestAbstract:
+* Now add a class `PetOwner_bookVisit_IntegTest` integration test, also inheriting from the `VisitModuleIntegTestAbstract:
+
[source,java]
-.Pet_bookVisit_IntegTest.java
+.PetOwner_bookVisit_IntegTest.java
----
-public class Pet_bookVisit_IntegTest extends VisitsModuleIntegTestAbstract {
+public class PetOwner_bookVisit_IntegTest extends VisitModuleIntegTestAbstract {
@BeforeEach
void setup() {
- fixtureScripts.run(new Pet_persona.PersistAll()); // <.>
+ fixtureScripts.run(new PetOwner_persona.PersistAll()); // <.>
}
@Test
public void happy_case() {
// given
- Pet somePet = fakeDataService.enums().anyOf(Pet_persona.class) // <.>
- .findUsing(serviceRegistry);
- List<Visit> before = visitRepository.findByPetOrderByVisitAtDesc(somePet);
+ PetOwner somePetOwner = fakeDataService.enums() // <.>
+ .anyOf(PetOwner_persona.class)
+ .findUsing(serviceRegistry);
+ Pet somePet = fakeDataService.collections()
+ .anyOf(somePetOwner.getPets());
+
+ List<Visit> before = visitRepository.findByPetOwner(somePetOwner);
+ assertThat(before).isEmpty();
// when
- LocalDateTime visitAt = clockService.getClock().nowAsLocalDateTime() // <.>
- .plusDays(fakeDataService.ints().between(1, 3));
- String reason = fakeDataService.strings().upper(40); // <3>
+ LocalDateTime visitAt = clockService.getClock().nowAsLocalDateTime() // <.>
+ .plusDays(fakeDataService.ints().between(1, 3));
- wrapMixin(Pet_bookVisit.class, somePet).act(visitAt, reason); // <.>
+ wrapMixin(PetOwner_bookVisit.class, somePetOwner).act(somePet, visitAt); // <.>
// then
- List<Visit> after = visitRepository.findByPetOrderByVisitAtDesc(somePet);
- after.removeAll(before);
- assertThat(after).hasSize(1); // <.>
+ List<Visit> after = visitRepository.findByPetOwner(somePetOwner);
+ assertThat(after).hasSize(1);
+
Visit visit = after.get(0);
- assertThat(visit.getPet()).isSameAs(somePet); // <.>
- assertThat(visit.getVisitAt()).isEqualTo(visitAt); // <6>
- assertThat(visit.getReason()).isEqualTo(reason); // <6>
+ assertThat(visit.getPet()).isSameAs(somePet); // <.>
+ assertThat(visit.getPet().getPetOwner()).isSameAs(somePetOwner); // <.>
+ assertThat(visit.getVisitAt()).isEqualTo(visitAt); // <6>
}
@Inject FakeDataService fakeDataService;
@@ -142,7 +154,7 @@ public class Pet_bookVisit_IntegTest extends VisitsModuleIntegTestAbstract {
}
----
<.> uses same fixture script used for prototyping to set up ``Pet``s and their ``PetOwner``s.
-<.> uses the xref:refguide:testing:index/fakedata/applib/services/FakeDataService.adoc[FakeDataService] to select a `Pet` persona at random and uses that person to look up the corresponding domain object.
+<.> uses the xref:refguide:testing:index/fakedata/applib/services/FakeDataService.adoc[FakeDataService] to select a random `PetOwner` and corresponding `Pet`
<.> sets up some randomised but valid argument values
<.> invokes the action, using the xref:refguide:applib:index/services/wrapper/WrapperFactory.adoc[WrapperFactory] to simulate the UI
<.> asserts that one new `Visit` has been created for the `Pet`.
@@ -150,58 +162,29 @@ public class Pet_bookVisit_IntegTest extends VisitsModuleIntegTestAbstract {
+
Run the test and check that it passes.
-* write an error scenario which checks that a reason has been provided:
-+
-[source,java]
-.Pet_bookVisit_IntegTest.java
-----
-@Test
-public void reason_is_required() {
-
- // given
- Pet somePet = fakeDataService.enums().anyOf(Pet_persona.class)
- .findUsing(serviceRegistry);
- List<Visit> before = visitRepository.findByPetOrderByVisitAtDesc(somePet);
-
- // when, then
- LocalDateTime visitAt = clockService.getClock().nowAsLocalDateTime()
- .plusDays(fakeDataService.ints().between(1, 3));
-
- assertThatThrownBy(() ->
- wrapMixin(Pet_bookVisit.class, somePet).act(visitAt, null)
- )
- .isInstanceOf(InvalidException.class)
- .hasMessage("'Reason' is mandatory");
-}
-----
* write an error scenario which checks that the `visitAt` date cannot be in the past:
+
[source,java]
-.Pet_bookVisit_IntegTest.java
+.PetOwner_bookVisit_IntegTest.java
----
@Test
public void cannot_book_in_the_past() {
// given
- Pet somePet = fakeDataService.enums().anyOf(Pet_persona.class)
+ PetOwner somePetOwner = fakeDataService.enums()
+ .anyOf(PetOwner_persona.class)
.findUsing(serviceRegistry);
- List<Visit> before = visitRepository.findByPetOrderByVisitAtDesc(somePet);
+ Pet somePet = fakeDataService.collections()
+ .anyOf(somePetOwner.getPets());
// when, then
LocalDateTime visitAt = clockService.getClock().nowAsLocalDateTime();
- String reason = fakeDataService.strings().upper(40);
assertThatThrownBy(() ->
- wrapMixin(Pet_bookVisit.class, somePet).act(visitAt, reason)
+ wrapMixin(PetOwner_bookVisit.class, somePetOwner).act(somePet, visitAt)
)
.isInstanceOf(InvalidException.class)
- .hasMessage("Must be in the future");
+ .hasMessage("Must book in the future");
}
----
-
-*
-
-
-
-
(causeway) 26/29: CAUSEWAY-2783 : fixes docs re: causewaystuff-tooling-cli.jar file
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 6d970c7479de01e203623b213fc7c34c25880191
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Tue May 28 08:28:31 2024 +0100
CAUSEWAY-2783 : fixes docs re: causewaystuff-tooling-cli.jar file
---
.run/causeway-tooling-cli (index).run.xml | 22 ------------
.run/causeway-tooling-cli (overview).run.xml | 42 ----------------------
.../ROOT/pages/release-process-prereqs.adoc | 4 +--
3 files changed, 2 insertions(+), 66 deletions(-)
diff --git a/.run/causeway-tooling-cli (index).run.xml b/.run/causeway-tooling-cli (index).run.xml
deleted file mode 100644
index 7845d8036a..0000000000
--- a/.run/causeway-tooling-cli (index).run.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<component name="ProjectRunConfigurationManager">
- <configuration default="false" name="causeway-tooling-cli (index)" type="Application" factoryName="Application">
- <option name="ALTERNATIVE_JRE_PATH" value="azul-21" />
- <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
- <option name="MAIN_CLASS_NAME" value="org.apache.causeway.tooling.cli.Cli" />
- <module name="causeway-tooling-cli" />
- <option name="PROGRAM_PARAMETERS" value="-p . -x antora/components/refguide-index index " />
- <extension name="net.ashald.envfile">
- <option name="IS_ENABLED" value="false" />
- <option name="IS_SUBST" value="false" />
- <option name="IS_PATH_MACRO_SUPPORTED" value="false" />
- <option name="IS_IGNORE_MISSING_FILES" value="false" />
- <option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
- <ENTRIES>
- <ENTRY IS_ENABLED="true" PARSER="runconfig" />
- </ENTRIES>
- </extension>
- <method v="2">
- <option name="Make" enabled="true" />
- </method>
- </configuration>
-</component>
\ No newline at end of file
diff --git a/.run/causeway-tooling-cli (overview).run.xml b/.run/causeway-tooling-cli (overview).run.xml
deleted file mode 100644
index 050f0423ed..0000000000
--- a/.run/causeway-tooling-cli (overview).run.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<component name="ProjectRunConfigurationManager">
- <configuration default="false" name="isis-tooling-cli (overview)" type="Application" factoryName="Application">
- <option name="ALTERNATIVE_JRE_PATH" value="C:\Program Files\OpenJDK\openjdk-11.0.7_10" />
- <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
- <option name="MAIN_CLASS_NAME" value="org.apache.isis.tooling.cli.Cli" />
- <module name="isis-tooling-cli" />
- <option name="PROGRAM_PARAMETERS" value="-p . -o antora/components/refguide-index overview" />
- <extension name="net.ashald.envfile">
- <option name="IS_ENABLED" value="false" />
- <option name="IS_SUBST" value="false" />
- <option name="IS_PATH_MACRO_SUPPORTED" value="false" />
- <option name="IS_IGNORE_MISSING_FILES" value="false" />
- <option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
- <ENTRIES>
- <ENTRY IS_ENABLED="true" PARSER="runconfig" />
- </ENTRIES>
- </extension>
- <method v="2">
- <option name="Make" enabled="true" />
- </method>
- </configuration>
- <configuration default="false" name="isis-tooling-cli (overview)" type="Application" factoryName="Application">
- <option name="ALTERNATIVE_JRE_PATH" value="C:\Program Files\OpenJDK\openjdk-11.0.7_10" />
- <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
- <option name="MAIN_CLASS_NAME" value="org.apache.isis.tooling.cli.Cli" />
- <module name="isis-tooling-cli" />
- <option name="PROGRAM_PARAMETERS" value="-p . -r core/adoc overview " />
- <extension name="net.ashald.envfile">
- <option name="IS_ENABLED" value="false" />
- <option name="IS_SUBST" value="false" />
- <option name="IS_PATH_MACRO_SUPPORTED" value="false" />
- <option name="IS_IGNORE_MISSING_FILES" value="false" />
- <option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
- <ENTRIES>
- <ENTRY IS_ENABLED="true" PARSER="runconfig" />
- </ENTRIES>
- </extension>
- <method v="2">
- <option name="Make" enabled="true" />
- </method>
- </configuration>
-</component>
\ No newline at end of file
diff --git a/antora/components/comguide/modules/ROOT/pages/release-process-prereqs.adoc b/antora/components/comguide/modules/ROOT/pages/release-process-prereqs.adoc
index 564a041d96..617db0e11b 100644
--- a/antora/components/comguide/modules/ROOT/pages/release-process-prereqs.adoc
+++ b/antora/components/comguide/modules/ROOT/pages/release-process-prereqs.adoc
@@ -168,13 +168,13 @@ mvn -D module-tooling install -Dskip.essential
In the regular `causeway` repo:
-* Create a symlink to the `causeway-tooling-cli` jar file
+* Create a symlink to the `causewaystuff-tooling-cli` jar file
+
[source,bash]
----
mkdir -p tooling/cli/target
pushd tooling/cli/target
-ln -s ../../../../../apache-causeway-committers/causewaystuff/tooling/cli/target/causewaystuff-tooling-cli-spring-boot.jar causeway-tooling-cli.jar
+ln -s ../../../../../apache-causeway-committers/causewaystuff/tooling/cli/target/causewaystuff-tooling-cli-spring-boot.jar causewaystuff-tooling-cli.jar
popd
----
+
(causeway) 15/29: CAUSEWAY-2873: 04-08
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 25f5400d3504f222c650ae17bb7b227c62d6bef3
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sun May 26 14:49:06 2024 +0100
CAUSEWAY-2873: 04-08
---
.../modules/petclinic/pages/040-pet-entity.adoc | 568 ++++++---------------
.../modules/petclinic/pages/100-todo.adoc | 2 +
2 files changed, 161 insertions(+), 409 deletions(-)
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 1bb6b01974..efe36851e5 100644
--- a/antora/components/tutorials/modules/petclinic/pages/040-pet-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/040-pet-entity.adoc
@@ -240,6 +240,9 @@ private String name;
</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`
@@ -412,381 +415,146 @@ public PetOwner removePet(@PetName final Pet pet) { // <1>
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
-
-UP TO HERE.
-
-Recall that our
-
-
-
-[#exercise-4-5-add-pets-remaining-properties]
-== Ex 4.5: Add Pet's remaining properties
-
-In this exercise we'll add the remaining properties for `Pet`.
-
-[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
-----
+[#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]
----
-git checkout tags/04-03-pet-remaining-properties
+git checkout tags/04-05-extend-fixture-data-to-add-in-pets
mvn clean install
mvn -pl spring-boot:run
----
-
=== Tasks
-* declare the `PetSpecies` enum:
+* update the enum constants of `PetOwner_persona`, adding a fourth parameter of pet names:
+
[source,java]
-.PetSpecies.java
-----
-public enum PetSpecies {
- Dog,
- Cat,
- Hamster,
- Budgerigar,
-}
-----
+.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"});
-* 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 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 the `petSpecies` property is mandatory, also update the constructor:
-+
-[source,java]
-.Pet.java
-----
-Pet(PetOwner petOwner, String name, PetSpecies petSpecies) {
- this.petOwner = petOwner;
- this.name = name;
- this.petSpecies = petSpecies;
-}
-----
-
-* 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 = "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-7-pet-title-and-dynamic-icons]
-== Ex 4.7: Pet title and dynamic icons
-
-
-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.
-
-
-=== Solution
-
-[source,bash]
-----
-git checkout tags/04-07-Pet-ui-customisation
-mvn clean install
-mvn -pl spring-boot:run
-----
-
-
-=== Tasks
-
-
-* 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();
-}
+// ...
+private final String[] petNames;
----
-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)
-
-* 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:
+* in the `PetOwner_persona.Builder` class, use the `petNames` to add pets to each owner:
+
[source,java]
-.Pet.java
-----
-public String iconName() {
- return getPetSpecies().name().toLowerCase();
-}
+.PetOwner_persona.java
----
+@Override
+protected PetOwner buildResult(final ExecutionContext ec) {
-* Run the application.
-You should find that the appropriate icon is selected based upon the species of the `Pet`.
+ // ...
+ Arrays.stream(persona.petNames).forEach(petOwner::addPet);
-* 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
-----
-causeway:
- viewer:
- wicket:
- max-title-length-in-standalone-tables: 10
- max-title-length-in-parented-tables: 10
-----
-+
-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%;
+ return petOwner;
}
----
+Run the application and confirm that each pet owner has one or several pets associated with them.
-=== 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-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.
-[#exercise-4-8-update-fixture-script-using-pet-personas]
-== Ex 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.
-
+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]
----
-git checkout tags/04-08-Pet-personas
+git checkout tags/04-06-unique-pet-names-validation
mvn clean install
mvn -pl spring-boot:run
----
-
=== Tasks
-* First we need to modify the `PetOwnerBuilder` to make it idempotent:
-+
[source,java]
-.PetOwnerBuilder.java
+.PetOwner.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;
+@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 + "'"; // <.>
}
-
- @Inject PetOwners petOwners;
+ 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`
+<.> A non-null value indicates is used as the reason the action cannot be invoked
+<.> If null is returned then the validation has succeeded.
-* Now we create a similar `PetBuilder` fixture script to add ``Pet``s through a `PetOwner`:
-+
-[source,java]
-.PetBuilder.java
-----
-@Accessors(chain = true)
-public class PetBuilder extends BuilderScriptWithResult<Pet> {
-
- @Getter @Setter String name;
- @Getter @Setter PetSpecies petSpecies;
- @Getter @Setter PetOwner_persona petOwner_persona;
+Run the application and confirm that the validation is working as you expect.
- @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();
- }
+[#exercise-4-7-add-pets-remaining-properties]
+== Ex 4.7: Add Pet's remaining properties
- 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
+In this exercise we'll add the remaining properties for `Pet`.
+Let's remind ourselves of the domain:
-* Now we create a "persona" enum for ``Pet``s:
-+
-[source,java]
-.Pet_persona.java
+[plantuml]
----
-@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);
+include::partial$skinparam.adoc[]
- @Getter private final String name;
- @Getter private final PetSpecies petSpecies;
- @Getter private final PetOwner_persona petOwner_persona;
+package pets {
- @Override
- public PetBuilder builder() {
- return new PetBuilder() // <.>
- .setName(name) // <.>
- .setPetSpecies(petSpecies)
- .setPetOwner_persona(petOwner_persona);
+ enum PetSpecies <<desc>> {
+ Dog
+ Cat
+ Hamster
+ Budgerigar
}
- @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);
+ class Pet <<ppt>> {
+ +id
+ ..
+ #petOwner
+ #name
+ ..
+ -species
+ -notes
+ ..
+ -version
}
- public static class PersistAll
- extends PersonaEnumPersistAll<Pet_persona, Pet> {
- public PersistAll() {
- super(Pet_persona.class);
- }
- }
}
-----
-<.> 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).
-* Finally, update the top-level `PetClinicDemo` to create both ``Pet``s and also ``PetOwner``s.
-+
-[source,java]
-.PetClinicDemo.java
-----
-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;
-}
+Pet "*" -u-> PetSpecies
----
-
-
-
-
-
-
-
-[#exercise-4-9-add-petowner-action-to-delete-a-pet]
-== Ex 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).
-
-
+So, we need to add a `species`, and some `notes`.
=== Solution
[source,bash]
----
-git checkout tags/04-09-PetOwner-deletePet-action
+git checkout tags/04-07-pet-remaining-properties
mvn clean install
mvn -pl spring-boot:run
----
@@ -794,145 +562,123 @@ mvn -pl spring-boot:run
=== Tasks
-+ create a new action mixins, `PetOwner_removePet`:
+* declare the `PetSpecies` enum:
+
[source,java]
-.PetOwner_removePet.java
+.PetSpecies.java
----
-@Action(
- semantics = SemanticsOf.IDEMPOTENT,
- commandPublishing = Publishing.ENABLED,
- executionPublishing = Publishing.ENABLED
-)
-@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;
+public enum PetSpecies {
+ Dog,
+ Cat,
+ Hamster,
+ Budgerigar,
}
----
-* To be explicit, add in an xref:refguide:applib:index/annotation/ActionLayout.adoc#sequence[@ActionLayout#sequence] for "addPet" also:
+* add in a reference to `PetSpecies`:
+
[source,java]
-.PetOwner_addPet.java
+.Pet.java
----
-// ...
-@ActionLayout(associateWith = "pets", sequence = "1")
-// ...
-public class PetOwner_addPet {
- // ...
-}
+@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
-* 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:
+* As the `petSpecies` property is mandatory, also update the `PetOwner#addPet` action:
+
[source,java]
-.PetOwner_removePet.java
+.PetOwner.java
----
-public List<String> choices0Act() {
- return petRepository.findByPetOwner(petOwner)
- .stream()
- .map(Pet::getName)
- .collect(Collectors.toList());
+@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 should xref:refguide:applib-methods:prefixes.adoc#disable[disable] (grey out) the `removePet` action if the `PetOwner` has no ``Pet``s:
+* 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_removePet.java
+.PetOwner_persona.java
----
-public String disableAct() {
- return petRepository.findByPetOwner(petOwner).isEmpty() ? "No pets" : null;
-}
+Arrays.stream(persona.petNames).forEach(petName -> {
+ PetSpecies randomSpecies = fakeDataService.enums().anyOf(PetSpecies.class);
+ petOwner.addPet(petName, randomSpecies);
+});
----
-* As a final refinement, if there is exactly one `Pet` then that could be the xref:refguide:applib-methods:prefixes.adoc#default[default]:
+* add in an optional `notes` property:
+
[source,java]
-.PetOwner_removePet.java
----
-public String default0Act() {
- List<String> names = choices0Act();
- return names.size() == 1 ? names.get(0) : null;
-}
+@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:
-=== Optional exercise
-
-NOTE: If you decide to do this optional exercise, make the changes on a git branch so that you can resume with the main flow of exercises later.
-
-If we wanted to work with multiple instances of the `pets` collection, we can use the xref:refguide:applib-methods:prefixes.adoc#choices[choices] method using the xref:refguide:applib:index/annotation/Action.adoc#choicesFrom[@Action#choicesFrom] attribute.
-
-Add this mixin to allow multiple ``Pet``s to be removed at the same time:
-
-[source,java]
-.PetOwner_removePets.java
+* 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
----
-@Action(
- semantics = SemanticsOf.IDEMPOTENT,
- commandPublishing = Publishing.ENABLED,
- executionPublishing = Publishing.ENABLED,
- choicesFrom = "pets" // <.>
-)
-@ActionLayout(associateWith = "pets", sequence = "2")
-@RequiredArgsConstructor
-public class PetOwner_removePets { // <.>
-
- private final PetOwner petOwner;
- public PetOwner act(final List<Pet> pets) { // <.>
- pets.forEach(repositoryService::remove);
- return petOwner;
- }
- public String disableAct() {
- return petRepository.findByPetOwner(petOwner).isEmpty() ? "No pets" : null;
- }
- // <.>
- @Inject PetRepository petRepository;
- @Inject RepositoryService repositoryService;
-}
+* update the column order file for `Pet#pets` collection:
++
+[source,text]
+.PetOwner#pets.columnOrder.txt
+----
+name
+species
+#notes
+#id
+#version
+#petOwner
----
-<.> Results in checkboxes in the table, allowing the user to optionally check one or more instances before invoking the action.
-<.> Renamed as the action now works with a list of ``Pet``s
-<.> Signature changed.
-<.> The `choices` method is removed.
+Run the application, confirm it runs up ok and that the new properties of `Pet` are present and correct.
-[#exercise-4-10-cleanup]
-== Ex 4.10: Cleanup
-Reviewing the contents of the `pets` module, we can see (in the solutions provided at least) that there are a few thing that still need some attention:
+[#exercise-4-8-dynamic-icons-for-pet]
+== Ex 4.8: Add dynamic icons for Pet
-* the classes and files for `Pet` are in the same package as for `PetOwner`; they probably should live in their own package
-* the "delete" action for `PetOwner` is not present in the UI, because its "associateWith" relates to a non-visible property
-* the "delete" action for `PetOwner` fails if there are ``Pet``s, due to a referential integrity issue.
+Now for a bit of UI candy.
-In this exercise we clean up these oversights.
+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]
----
-git checkout tags/04-10-pets-module-cleanup
+git checkout tags/04-08-dynamic-icons
mvn clean install
mvn -pl spring-boot:run
----
@@ -940,19 +686,23 @@ mvn -pl spring-boot:run
=== Tasks
-Just check out the tag above and inspect the fixes:
-* the `Pet` entity, `PetRepository` and related UI files have been moved to `petclinic.modules.pets.dom.pet` package
+* download additional icons for each of the `PetSpecies` (dog, cat, hamster, budgie)
-* the `PetOwner_pet`, `PetOwner_addPet` and `PetOwner_removePet` mixins have also been moved.
-+
-This means that `PetOwner` is actually unaware of the fact that there are associated ``Pet``s.
-This abliity to control the direction of dependencies is very useful for ensuring modularity.
+* save these icons as `Pet-dog.png`, `Pet-cat.png` and so on, ie the pet species as suffix.
-* the ``PetOwner``'s `delete` action has been refactored into a mixin, and also moved to the `pets` package so that it will delete the child ``Pet``s first.
+* implement the xref:refguide:applib-methods:ui-hints.adoc#iconName[iconName()] method as follows:
+
-Also fixes tests.
+[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`.
-* the fixtures for `PetOwner` and `Pet` have also been moved into their own packages.
-* the tear down fixture for `PetsModule` has been updated to also delete from the `Pet` entity.
diff --git a/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc b/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
index a3377a3e3a..25edb2f568 100644
--- a/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
@@ -6,6 +6,8 @@ TODO: ideas for future steps:
validate pet name is unique within Pet
+refactor addPet to be an inline-mixin.
+
visit
- need some words about adding VisitRepository
(causeway) 07/29: CAUSEWAY-2873: 03-09
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit bcd2e8b4a4763cf2657c0147e7d39bc028e91e28
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sat May 25 14:46:45 2024 +0100
CAUSEWAY-2873: 03-09
---
.../petclinic/pages/030-petowner-entity.adoc | 83 ++++++++++++++++------
.../modules/petclinic/pages/100-todo.adoc | 3 +
2 files changed, 65 insertions(+), 21 deletions(-)
diff --git a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
index 383149ad87..529472a39d 100644
--- a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
@@ -517,11 +517,10 @@ 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.
-+
-using the Spring boot profile trick from before:
+We'll use the "dev" profile introduced earlier:
+
[source,yaml]
-.application-custom.yaml
+.application-dev.yaml
----
causeway:
viewer:
@@ -529,7 +528,7 @@ causeway:
prompt-style: dialog
----
+
-Remember to activate this new profile (`-Dspring.profiles.active=dev,custom`) and confirm that the `updateName` prompt now uses a sidebar 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.
+
@@ -567,27 +566,21 @@ causeway:
-[#exercise-3-7-derived-name-property]
-== Ex 3.7: Derived name property
-
-The ``PetOwner``'s `firstName` and `lastName` properties are updated using the `updateName` action, but when the action's button is invoked, it only "replaces" the `lastName` property:
-
-image::Owner-updateName-prompt.png[width="400px"]
-
-In this exercise we'll improve the UI by introducing a derived `name` property and then hiding the `firstName` and `lastName`:
+[#exercise-3-9-derived-days-since-last-visit-property]
+== Ex 3.9: Derived 'days since last visit' property
-image::Owner-name.png[width="400px"]
+Not every property has to persisted, nor editable (indeed most properties are not editable).
-When `PetOwner#updateName` is invoked, the prompt we'll want see is:
+For example, it might be useful to calculate the number of days since the pet owner last visited; perhaps for marketing purposes.
-image::Owner-name-updated.png[width="400px"]
+In this exercise we'll see how easy it is to create such a derived property.
=== Solution
[source,bash]
----
-git checkout tags/03-07-derived-PetOwner-name
+git checkout tags/03-09-derived-days-since-last-visit-property
mvn clean install
mvn -pl spring-boot:run
----
@@ -595,18 +588,66 @@ mvn -pl spring-boot:run
=== 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)
-* Add `getName()` as the derived `name` property:
+* implement `getDaysSinceLastVisit()` method, calculating the number of days since "today".
+
[source,java]
.PetOwner.java
----
-@Transient
-@PropertyLayout(fieldSetId = "name", sequence = "1")
-public String getName() {
- return getFirstName() + " " + getLastName();
+@Property
+@PropertyLayout(fieldSetId = LayoutConstants.FieldSetId.DETAILS, sequence = "3.1") // <.>
+public long getDaysSinceLastVisit() {
+ return Math.abs(ChronoUnit.DAYS.between(getLastVisit(), clockService.getClock().nowAsLocalDate()));
}
----
+<.> 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
+----
+
* Hide the `lastName` and `firstName` properties, using `@Property(hidden=...)`.
We can also remove the `@PropertyLayout` annotation.
diff --git a/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc b/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
index b8603a2c3b..66bd2a3312 100644
--- a/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
@@ -5,3 +5,6 @@ TODO: ideas for future steps:
add validation for telephone number and email address.
+
+
+PetOwner#daysSinceLastVisit could be made into a mixin - eg if these marketing analytics were the responsibility of some other module.
(causeway) 16/29: CAUSEWAY-2873: 05-01
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit f45b231503ebb25c425893f8d9c94d2eb794076c
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sun May 26 17:06:58 2024 +0100
CAUSEWAY-2873: 05-01
---
.../modules/petclinic/pages/050-visit-entity.adoc | 62 ++++++++++++++++------
1 file changed, 45 insertions(+), 17 deletions(-)
diff --git a/antora/components/tutorials/modules/petclinic/pages/050-visit-entity.adoc b/antora/components/tutorials/modules/petclinic/pages/050-visit-entity.adoc
index de92e13f46..05b25751be 100644
--- a/antora/components/tutorials/modules/petclinic/pages/050-visit-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/050-visit-entity.adoc
@@ -26,47 +26,75 @@ mvn clean install
mvn -pl spring-boot:run
----
-
-
-
=== Tasks
-Just check out the tag above and inspect the changes:
+To save time, just checkout the solution tag above and review the git history to see the files and classes that were created:
-* A new `petclinic-module-visits` maven module has been created
+* A new `simpleapp-jpa-module-visit` maven module was defined in the top-level `pom.xml`
-* its `pom.xml` declares a dependency on the `petclinic-module-pets` maven module
+* the `pom.xml` for the new visit module itself was created, and referenced from the top-level `pom.xml` as a child `<module>`
-* the top-level pom.xml declares the new Maven module and references it
-
-* the `VisitsModule` class is a Spring `@Configuration` bean that resides in the root of the visits module, and declares an app dependency on the pets module that mirrors the maven dependency:
+* a `VisitModule` class was created, defining the module to Causeway as a Spring `@Configuration`:
+
[source,java]
-.VisitsModule.java
+.VisitModule.java
----
@Configuration
+@Import({
+ CausewayModuleExtPdfjsApplib.class,
+ CausewayModuleExtFullCalendarApplib.class,
+ CausewayModuleTestingFakeDataApplib.class,
+ CausewayModulePersistenceJpaApplib.class,
+})
@ComponentScan
-@Import(PetsModule.class)
@EnableJpaRepositories
-@EntityScan(basePackageClasses = {VisitsModule.class})
-public class VisitsModule implements ModuleWithFixtures {
+@EntityScan(basePackageClasses = {VisitModule.class})
+public class VisitModule implements ModuleWithFixtures {
+
+ public static final String NAMESPACE = "visit";
+ public static final String SCHEMA = "visit";
@Override
public FixtureScript getTeardownFixture() {
- return new FixtureScript() {
+ return new TeardownFixtureJpaAbstract() {
@Override
protected void execute(ExecutionContext executionContext) {
- // nothing to do
}
};
}
}
----
-* the webapp Maven module now depends on the new visits maven module, and the top-level `ApplicationModule` Spring `@Configuration` bean now depends upon `VisitsModule` rather than `PetsModule`
+* the top-level `application.properties` was updated to ensure that the new `visit` schema is created (when running with an in-memory database)
++
+[source,properties]
+.application.properties
+----
+causeway.persistence.schema.auto-create-schemas=\
+ petowner,visit,simple,...
+----
+
+
+== next exercise:
+
+set up dependencies
+- maven
+- spring
+
+reference new module in AppManifest
+- remove transitive dependencies
+
+
-It still depends upon `PetsModule`, but now as a transitive dependency.
+[source,java]
+.VisitsModule.java
+----
+----
++
+[source,java]
+.ApplicationModule.java
+----
+----
(causeway) 10/29: CAUSEWAY-2873: attempts to improve styling of asciidoc plugin
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit eb64a7f7cb830c6082252be9499cebd680cf2af3
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sun May 26 10:29:29 2024 +0100
CAUSEWAY-2873: attempts to improve styling of asciidoc plugin
... though with no success.
---
.asciidoctorconfig | 6 +
antora/.asciidoctor-styles/asciidoc-classic.css | 527 ++++++++++++++++++++++++
antora/.asciidoctor-styles/boot-flatly.css | 350 ++++++++++++++++
3 files changed, 883 insertions(+)
diff --git a/.asciidoctorconfig b/.asciidoctorconfig
index 6732835d4d..68dced8e96 100644
--- a/.asciidoctorconfig
+++ b/.asciidoctorconfig
@@ -10,3 +10,9 @@
// see discussion here https://github.com/asciidoctor/asciidoctor-intellij-plugin/issues/548
:kroki-default-options: inline
+:linkcss:
+:stylesdir: {asciidoctorconfigdir}/antora/.asciidoctor-styles
+:stylesheet: asciidoc-classic.css
+// :stylesdir: https://raw.githubusercontent.com/darshandsoni/asciidoctor-skins/gh-pages/css
+// :stylesheet: asciidoc-classic.css
+
diff --git a/antora/.asciidoctor-styles/asciidoc-classic.css b/antora/.asciidoctor-styles/asciidoc-classic.css
new file mode 100644
index 0000000000..be88313e92
--- /dev/null
+++ b/antora/.asciidoctor-styles/asciidoc-classic.css
@@ -0,0 +1,527 @@
+/* Shared CSS for AsciiDoc xhtml11 and html5 backends */
+
+/* Default font. */
+body {
+ font-family: Georgia,serif;
+}
+
+/* Title font. */
+h1, h2, h3, h4, h5, h6,
+div.title, caption.title,
+thead, p.table.header,
+#toctitle,
+#author, #revnumber, #revdate, #revremark,
+#footer {
+ font-family: Arial,Helvetica,sans-serif;
+}
+
+body {
+ margin: 1em 5% 1em 5%;
+}
+
+a {
+ color: blue;
+ text-decoration: underline;
+}
+a:visited {
+ color: fuchsia;
+}
+
+em {
+ font-style: italic;
+ color: navy;
+}
+
+strong {
+ font-weight: bold;
+ color: #083194;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ color: #527bbd;
+ margin-top: 1.2em;
+ margin-bottom: 0.5em;
+ line-height: 1.3;
+}
+
+h1, h2, h3 {
+ border-bottom: 2px solid silver;
+}
+h2 {
+ padding-top: 0.5em;
+}
+h3 {
+ float: left;
+}
+h3 + * {
+ clear: left;
+}
+h5 {
+ font-size: 1.0em;
+}
+
+div.sectionbody {
+ margin-left: 0;
+}
+
+hr {
+ border: 1px solid silver;
+}
+
+p {
+ margin-top: 0.5em;
+ margin-bottom: 0.5em;
+}
+
+ul, ol, li > p {
+ margin-top: 0;
+}
+ul > li { color: #aaa; }
+ul > li > * { color: black; }
+
+.monospaced, code, pre {
+ font-family: "Courier New", Courier, monospace;
+ font-size: inherit;
+ color: navy;
+ padding: 0;
+ margin: 0;
+}
+pre {
+ white-space: pre-wrap;
+}
+
+#author {
+ color: #527bbd;
+ font-weight: bold;
+ font-size: 1.1em;
+}
+#email {
+}
+#revnumber, #revdate, #revremark {
+}
+
+#footer {
+ font-size: small;
+ border-top: 2px solid silver;
+ padding-top: 0.5em;
+ margin-top: 4.0em;
+}
+#footer-text {
+ float: left;
+ padding-bottom: 0.5em;
+}
+#footer-badges {
+ float: right;
+ padding-bottom: 0.5em;
+}
+
+#preamble {
+ margin-top: 1.5em;
+ margin-bottom: 1.5em;
+}
+div.imageblock, div.exampleblock, div.verseblock,
+div.quoteblock, div.literalblock, div.listingblock, div.sidebarblock,
+div.admonitionblock {
+ margin-top: 1.0em;
+ margin-bottom: 1.5em;
+}
+div.admonitionblock {
+ margin-top: 2.0em;
+ margin-bottom: 2.0em;
+ margin-right: 10%;
+ color: #606060;
+}
+
+div.content { /* Block element content. */
+ padding: 0;
+}
+
+/* Block element titles. */
+div.title, caption.title {
+ color: #527bbd;
+ font-weight: bold;
+ text-align: left;
+ margin-top: 1.0em;
+ margin-bottom: 0.5em;
+}
+div.title + * {
+ margin-top: 0;
+}
+
+td div.title:first-child {
+ margin-top: 0.0em;
+}
+div.content div.title:first-child {
+ margin-top: 0.0em;
+}
+div.content + div.title {
+ margin-top: 0.0em;
+}
+
+div.sidebarblock > div.content {
+ background: #ffffee;
+ border: 1px solid #dddddd;
+ border-left: 4px solid #f0f0f0;
+ padding: 0.5em;
+}
+
+div.listingblock > div.content {
+ border: 1px solid #dddddd;
+ border-left: 5px solid #f0f0f0;
+ background: #f8f8f8;
+ padding: 0.5em;
+}
+
+div.quoteblock, div.verseblock {
+ padding-left: 1.0em;
+ margin-left: 1.0em;
+ margin-right: 10%;
+ border-left: 5px solid #f0f0f0;
+ color: #888;
+}
+
+div.quoteblock > div.attribution {
+ padding-top: 0.5em;
+ text-align: right;
+}
+
+div.verseblock > pre.content {
+ font-family: inherit;
+ font-size: inherit;
+}
+div.verseblock > div.attribution {
+ padding-top: 0.75em;
+ text-align: left;
+}
+/* DEPRECATED: Pre version 8.2.7 verse style literal block. */
+div.verseblock + div.attribution {
+ text-align: left;
+}
+
+div.admonitionblock .icon {
+ vertical-align: top;
+ font-size: 1.1em;
+ font-weight: bold;
+ text-decoration: underline;
+ color: #527bbd;
+ padding-right: 0.5em;
+}
+div.admonitionblock td.content {
+ padding-left: 0.5em;
+ border-left: 3px solid #dddddd;
+}
+
+div.exampleblock > div.content {
+ border-left: 3px solid #dddddd;
+ padding-left: 0.5em;
+}
+
+div.imageblock div.content { padding-left: 0; }
+span.image img { border-style: none; vertical-align: text-bottom; }
+a.image:visited { color: white; }
+
+dl {
+ margin-top: 0.8em;
+ margin-bottom: 0.8em;
+}
+dt {
+ margin-top: 0.5em;
+ margin-bottom: 0;
+ font-style: normal;
+ color: navy;
+}
+dd > *:first-child {
+ margin-top: 0.1em;
+}
+
+ul, ol {
+ list-style-position: outside;
+}
+ol.arabic {
+ list-style-type: decimal;
+}
+ol.loweralpha {
+ list-style-type: lower-alpha;
+}
+ol.upperalpha {
+ list-style-type: upper-alpha;
+}
+ol.lowerroman {
+ list-style-type: lower-roman;
+}
+ol.upperroman {
+ list-style-type: upper-roman;
+}
+
+div.compact ul, div.compact ol,
+div.compact p, div.compact p,
+div.compact div, div.compact div {
+ margin-top: 0.1em;
+ margin-bottom: 0.1em;
+}
+
+tfoot {
+ font-weight: bold;
+}
+td > div.verse {
+ white-space: pre;
+}
+
+div.hdlist {
+ margin-top: 0.8em;
+ margin-bottom: 0.8em;
+}
+div.hdlist tr {
+ padding-bottom: 15px;
+}
+dt.hdlist1.strong, td.hdlist1.strong {
+ font-weight: bold;
+}
+td.hdlist1 {
+ vertical-align: top;
+ font-style: normal;
+ padding-right: 0.8em;
+ color: navy;
+}
+td.hdlist2 {
+ vertical-align: top;
+}
+div.hdlist.compact tr {
+ margin: 0;
+ padding-bottom: 0;
+}
+
+.comment {
+ background: yellow;
+}
+
+.footnote, .footnoteref {
+ font-size: 0.8em;
+}
+
+span.footnote, span.footnoteref {
+ vertical-align: super;
+}
+
+#footnotes {
+ margin: 20px 0 20px 0;
+ padding: 7px 0 0 0;
+}
+
+#footnotes div.footnote {
+ margin: 0 0 5px 0;
+}
+
+#footnotes hr {
+ border: none;
+ border-top: 1px solid silver;
+ height: 1px;
+ text-align: left;
+ margin-left: 0;
+ width: 20%;
+ min-width: 100px;
+}
+
+div.colist td {
+ padding-right: 0.5em;
+ padding-bottom: 0.3em;
+ vertical-align: top;
+}
+div.colist td img {
+ margin-top: 0.3em;
+}
+
+@media print {
+ #footer-badges { display: none; }
+}
+
+#toc {
+ margin-bottom: 2.5em;
+}
+
+#toctitle {
+ color: #527bbd;
+ font-size: 1.1em;
+ font-weight: bold;
+ margin-top: 1.0em;
+ margin-bottom: 0.1em;
+}
+
+div.toclevel0, div.toclevel1, div.toclevel2, div.toclevel3, div.toclevel4 {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+div.toclevel2 {
+ margin-left: 2em;
+ font-size: 0.9em;
+}
+div.toclevel3 {
+ margin-left: 4em;
+ font-size: 0.9em;
+}
+div.toclevel4 {
+ margin-left: 6em;
+ font-size: 0.9em;
+}
+
+span.aqua { color: aqua; }
+span.black { color: black; }
+span.blue { color: blue; }
+span.fuchsia { color: fuchsia; }
+span.gray { color: gray; }
+span.green { color: green; }
+span.lime { color: lime; }
+span.maroon { color: maroon; }
+span.navy { color: navy; }
+span.olive { color: olive; }
+span.purple { color: purple; }
+span.red { color: red; }
+span.silver { color: silver; }
+span.teal { color: teal; }
+span.white { color: white; }
+span.yellow { color: yellow; }
+
+span.aqua-background { background: aqua; }
+span.black-background { background: black; }
+span.blue-background { background: blue; }
+span.fuchsia-background { background: fuchsia; }
+span.gray-background { background: gray; }
+span.green-background { background: green; }
+span.lime-background { background: lime; }
+span.maroon-background { background: maroon; }
+span.navy-background { background: navy; }
+span.olive-background { background: olive; }
+span.purple-background { background: purple; }
+span.red-background { background: red; }
+span.silver-background { background: silver; }
+span.teal-background { background: teal; }
+span.white-background { background: white; }
+span.yellow-background { background: yellow; }
+
+span.big { font-size: 2em; }
+span.small { font-size: 0.6em; }
+
+span.underline { text-decoration: underline; }
+span.overline { text-decoration: overline; }
+span.line-through { text-decoration: line-through; }
+
+div.unbreakable { page-break-inside: avoid; }
+
+
+/*
+ * xhtml11 specific
+ *
+ * */
+
+div.tableblock {
+ margin-top: 1.0em;
+ margin-bottom: 1.5em;
+}
+div.tableblock > table {
+ border: 3px solid #527bbd;
+}
+thead, p.table.header {
+ font-weight: bold;
+ color: #527bbd;
+}
+p.table {
+ margin-top: 0;
+}
+/* Because the table frame attribute is overriden by CSS in most browsers. */
+div.tableblock > table[frame="void"] {
+ border-style: none;
+}
+div.tableblock > table[frame="hsides"] {
+ border-left-style: none;
+ border-right-style: none;
+}
+div.tableblock > table[frame="vsides"] {
+ border-top-style: none;
+ border-bottom-style: none;
+}
+
+
+/*
+ * html5 specific
+ *
+ * */
+
+table.tableblock {
+ margin-top: 1.0em;
+ margin-bottom: 1.5em;
+}
+thead, p.tableblock.header {
+ font-weight: bold;
+ color: #527bbd;
+}
+p.tableblock {
+ margin-top: 0;
+}
+table.tableblock {
+ border-width: 3px;
+ border-spacing: 0px;
+ border-style: solid;
+ border-color: #527bbd;
+ border-collapse: collapse;
+}
+th.tableblock, td.tableblock {
+ border-width: 1px;
+ padding: 4px;
+ border-style: solid;
+ border-color: #527bbd;
+}
+
+table.tableblock.frame-topbot {
+ border-left-style: hidden;
+ border-right-style: hidden;
+}
+table.tableblock.frame-sides {
+ border-top-style: hidden;
+ border-bottom-style: hidden;
+}
+table.tableblock.frame-none {
+ border-style: hidden;
+}
+
+th.tableblock.halign-left, td.tableblock.halign-left {
+ text-align: left;
+}
+th.tableblock.halign-center, td.tableblock.halign-center {
+ text-align: center;
+}
+th.tableblock.halign-right, td.tableblock.halign-right {
+ text-align: right;
+}
+
+th.tableblock.valign-top, td.tableblock.valign-top {
+ vertical-align: top;
+}
+th.tableblock.valign-middle, td.tableblock.valign-middle {
+ vertical-align: middle;
+}
+th.tableblock.valign-bottom, td.tableblock.valign-bottom {
+ vertical-align: bottom;
+}
+
+
+/*
+ * manpage specific
+ *
+ * */
+
+body.manpage h1 {
+ padding-top: 0.5em;
+ padding-bottom: 0.5em;
+ border-top: 2px solid silver;
+ border-bottom: 2px solid silver;
+}
+body.manpage h2 {
+ border-style: none;
+}
+body.manpage div.sectionbody {
+ margin-left: 3em;
+}
+
+@media print {
+ body.manpage div#toc { display: none; }
+}
diff --git a/antora/.asciidoctor-styles/boot-flatly.css b/antora/.asciidoctor-styles/boot-flatly.css
new file mode 100644
index 0000000000..e98bf9e8ec
--- /dev/null
+++ b/antora/.asciidoctor-styles/boot-flatly.css
@@ -0,0 +1,350 @@
+/* Based on Flatly from Bootswatch (https://bootswatch.com/flatly/) */
+@import url("//fonts.googleapis.com/css?family=Lato:400,700,400italic");
+
+/* document body (contains all content) */
+body {
+ font-family: "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 15px;
+ line-height: 1.42857143;
+ color: #2c3e50;
+ background-color: #ffffff;
+ margin-left: 10%;
+ margin-right: 10%;
+}
+
+/* document header (contains title etc) */
+#header {
+ width: 100%;
+}
+#header>h1 {
+ border-bottom: 1px solid #ddddd8;
+ padding-bottom: 8px;
+}
+
+/* headings */
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-family: "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 400;
+ line-height: 1.1;
+ color: inherit;
+}
+h1,
+h2,
+h3 {
+ margin-top: 21px;
+ margin-bottom: 10.5px;
+}
+h4,
+h5,
+h6 {
+ margin-top: 10.5px;
+ margin-bottom: 10.5px;
+}
+h1 {
+ font-size: 4em;
+/* font-size: 39px; */
+}
+h2 {
+ font-size: 32px;
+}
+h3 {
+ font-size: 26px;
+}
+h4 {
+ font-size: 19px;
+}
+h5 {
+ font-size: 15px;
+}
+h6 {
+ font-size: 13px;
+}
+
+/* plain paragraph text */
+.paragraph {
+/* font-family: sans-serif; */
+ margin: 0 0 10.5px;
+}
+p {
+/* font-family: sans-serif; */
+ margin: 0 0 10.5px;
+}
+
+/* blockquote text */
+.quoteblock {
+ font-style: italic;
+}
+blockquote {
+ padding: 10.5px 21px;
+ margin: 0 0 21px;
+ font-size: 18.75px;
+ border-left: 5px solid #ecf0f1;
+}
+blockquote p:last-child,
+blockquote ul:last-child,
+blockquote ol:last-child {
+ margin-bottom: 0;
+}
+.blockquote-reverse,
+blockquote.pull-right {
+ padding-right: 15px;
+ padding-left: 0;
+ border-right: 5px solid #ecf0f1;
+ border-left: 0;
+ text-align: right;
+}
+.blockquote-reverse footer:before,
+blockquote.pull-right footer:before,
+.blockquote-reverse small:before,
+blockquote.pull-right small:before,
+.blockquote-reverse .small:before,
+blockquote.pull-right .small:before {
+ content: '';
+}
+.blockquote-reverse footer:after,
+blockquote.pull-right footer:after,
+.blockquote-reverse small:after,
+blockquote.pull-right small:after,
+.blockquote-reverse .small:after,
+blockquote.pull-right .small:after {
+ content: '\00A0 \2014';
+}
+
+/* blockquote attribution text */
+.attribution,
+.cite,
+blockquote footer,
+blockquote small,
+blockquote .small {
+ display: block;
+ font-size: 80%;
+ line-height: 1.42857143;
+ color: #b4bcc2;
+}
+.attribution:before,
+blockquote footer:before,
+blockquote small:before,
+blockquote .small:before {
+ content: '\2014 \00A0';
+}
+
+/* unordered list */
+ul, ol {
+ margin-top: 0;
+ margin-bottom: 10.5px;
+}
+ul ul,
+ol ul,
+ul ol,
+ol ol {
+ margin-bottom: 0;
+}
+
+
+/* links */
+a {
+ color: #18bc9c;
+ background-color: transparent;
+ text-decoration: none;
+}
+a:hover,
+a:focus {
+ color: #18bc9c;
+ text-decoration: underline;
+}
+a:focus {
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+
+/* horizontal rules */
+hr {
+ margin-top: 21px;
+ margin-bottom: 21px;
+ border: 0;
+ border-top: 1px solid #ecf0f1;
+}
+
+/* table */
+table {
+ background-color: transparent;
+ width: 100%;
+ max-width: 100%;
+ margin-bottom: 21px;
+ border-collapse: collapse;
+}
+table col[class*="col-"] {
+ position: static;
+ float: none;
+ display: table-column;
+}
+table td[class*="col-"],
+table th[class*="col-"] {
+ position: static;
+ float: none;
+ display: table-cell;
+}
+
+/* table caption */
+caption {
+ padding-top: 8px;
+ padding-bottom: 8px;
+ color: #b4bcc2;
+ text-align: left;
+}
+
+/* table header row */
+thead {
+ border-bottom: 2px solid #ecf0f1;
+}
+
+/* table header cell */
+th {
+ text-align: left;
+ padding-left: 8px;
+}
+
+/* table footer */
+tfoot {
+ color: #807F81;
+ border-top: 1px solid #ecf0f1;
+}
+
+/* table cell */
+td {
+ border-top: 1px solid #ecf0f1;
+}
+td p {
+ margin: auto;
+ padding: 8px;
+}
+
+/* table body */
+tbody > tr:nth-of-type(odd) {
+ background-color: #f9f9f9;
+}
+tbody > tr:hover {
+ background-color: #ecf0f1;
+}
+
+/* inline code */
+code,
+kbd,
+pre,
+samp {
+ font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
+}
+code {
+ padding: 2px 4px;
+ font-size: 90%;
+ color: #c7254e;
+ background-color: #f9f2f4;
+ border-radius: 4px;
+}
+kbd {
+ padding: 2px 4px;
+ font-size: 90%;
+ color: #ffffff;
+ background-color: #333333;
+ border-radius: 0;
+ -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25);
+ box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+kbd kbd {
+ padding: 0;
+ font-size: 100%;
+ font-weight: bold;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+}
+pre {
+ display: block;
+ padding: 10.5px;
+ margin: 0 0 10.5px;
+ font-size: 15px;
+ line-height: 1.42857143;
+ word-break: break-all;
+ word-wrap: break-word;
+ color: #333333;
+ background-color: #f5f5f5;
+ border: 1px solid #cccccc;
+ border-radius: 4px;
+}
+pre code {
+ padding: 0;
+ font-size: inherit;
+ color: inherit;
+ white-space: pre-wrap;
+ background-color: transparent;
+ border-radius: 0;
+}
+
+/* image */
+img {
+ max-width: 100%;
+ vertical-align: middle;
+}
+
+/* footer section */
+#footer {
+ margin-top: 22px;
+ padding: 14px 16px;
+ color: white;
+ background-color: #2c3e50;
+}
+
+/* responsiveness fixes */
+video {
+ max-width: 100%;
+}
+
+/* table of Contents sidebar */
+#toctitle {
+ color: #ffffff;
+}
+
+#toc ul {
+ display: inline;
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+}
+
+#toc li {
+ display: block;
+}
+
+#toc a {
+ background-color: #2c3e50;
+ float: left;
+ color: white;
+ text-align: center;
+ padding: 14px 16px;
+ text-decoration: none;
+}
+
+#toc li a:hover {
+ background-color: #1a242f;
+ text-decoration: none;
+}
+
+#toc:after {
+ content: " ";
+ visibility: hidden;
+ display: block;
+ height: 0;
+ clear: both;
+}
+
+@media all and (max-width: 600px) {
+ table {
+ width: 55vw !important;
+ font-size: 3vw;
+ }
+}
(causeway) 23/29: updates .sh to be executable
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 6f2c7f5f3235f18859bcddb0f96b1eb16de880ae
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Mon May 27 18:10:51 2024 +0100
updates .sh to be executable
---
build-core.sh | 0
build-demo-app.sh | 0
build-site.sh | 0
build-smoketests.sh | 0
build-tooling.sh | 0
build.sh | 0
changed.sh | 0
dependency-graph.sh | 0
docker-run-demo.sh | 0
enhance.sh | 0
gitcommit.sh | 0
github-pr.sh | 0
gitrmb.sh | 0
preview.sh | 0
serve-timeline.sh | 0
sync-adoc.sh | 0
16 files changed, 0 insertions(+), 0 deletions(-)
diff --git a/build-core.sh b/build-core.sh
old mode 100644
new mode 100755
diff --git a/build-demo-app.sh b/build-demo-app.sh
old mode 100644
new mode 100755
diff --git a/build-site.sh b/build-site.sh
old mode 100644
new mode 100755
diff --git a/build-smoketests.sh b/build-smoketests.sh
old mode 100644
new mode 100755
diff --git a/build-tooling.sh b/build-tooling.sh
old mode 100644
new mode 100755
diff --git a/build.sh b/build.sh
old mode 100644
new mode 100755
diff --git a/changed.sh b/changed.sh
old mode 100644
new mode 100755
diff --git a/dependency-graph.sh b/dependency-graph.sh
old mode 100644
new mode 100755
diff --git a/docker-run-demo.sh b/docker-run-demo.sh
old mode 100644
new mode 100755
diff --git a/enhance.sh b/enhance.sh
old mode 100644
new mode 100755
diff --git a/gitcommit.sh b/gitcommit.sh
old mode 100644
new mode 100755
diff --git a/github-pr.sh b/github-pr.sh
old mode 100644
new mode 100755
diff --git a/gitrmb.sh b/gitrmb.sh
old mode 100644
new mode 100755
diff --git a/preview.sh b/preview.sh
old mode 100644
new mode 100755
diff --git a/serve-timeline.sh b/serve-timeline.sh
old mode 100644
new mode 100755
diff --git a/sync-adoc.sh b/sync-adoc.sh
old mode 100644
new mode 100755
(causeway) 12/29: CAUSEWAY-2873: 03-14 (2)
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 5fc80f62dda26c86bdcb3f46f6d0c79ef8b322a5
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sun May 26 10:52:47 2024 +0100
CAUSEWAY-2873: 03-14 (2)
---
.../tutorials/modules/petclinic/pages/030-petowner-entity.adoc | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
index 496b871771..179a312512 100644
--- a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
@@ -1086,7 +1086,7 @@ This has the benefit of being dynamic; we can move fields around in the layout w
[source,bash]
----
-git checkout tags/03-14-PetOwner-fieldsets
+git checkout tags/03-14-use-layout-xml-for-ui-semantics
mvn clean install
mvn -pl spring-boot:run
----
(causeway) 03/29: CAUSEWAY-2873: 02-05, 03-04
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit d2f1d9934aadfe049ec24c86d07eb054f0ad61a8
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Fri May 24 07:11:05 2024 +0100
CAUSEWAY-2873: 02-05, 03-04
---
.gitignore | 3 +
.../petclinic/pages/020-the-petclinic-domain.adoc | 52 ++++++++-
.../petclinic/pages/030-petowner-entity.adoc | 116 +++++++++++++++++++--
.../modules/petclinic/pages/100-todo.adoc | 4 +
4 files changed, 162 insertions(+), 13 deletions(-)
diff --git a/.gitignore b/.gitignore
index 69fc1a2cdd..5a54c9a8f9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
+# Linux
+nohup.out
+
# IDEA
.idea
*.iml
diff --git a/antora/components/tutorials/modules/petclinic/pages/020-the-petclinic-domain.adoc b/antora/components/tutorials/modules/petclinic/pages/020-the-petclinic-domain.adoc
index 72d06a82ee..f7b58db22f 100644
--- a/antora/components/tutorials/modules/petclinic/pages/020-the-petclinic-domain.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/020-the-petclinic-domain.adoc
@@ -337,6 +337,9 @@ The xref:testing:fixtures:about.adoc#personas-and-builders[persona] pattern lets
In this exercise we'll modify the `PetOwners_persona` (copied from `SimpleObjects_persona` earlier) to create more realistic data.
+We'll also update the existing top-level `DomainAppDemo` fixture script, so that it will recreate our new ``PetOwner``s.
+This will allow us to quickly create a whole set of `PetOwner` objects from the "Prototyping" menu.
+
=== Solution
@@ -349,7 +352,50 @@ mvn -pl spring-boot:run
=== Tasks
-* locate the `PetOwners_persona` class
-* refactor the enum constants and values to create 10 realistic pet owner names
-* (optional) we'll use the associated `.pdf` to store a "veterinary clinic service agreement" for each pet owner.
+* locate the `PetOwners_persona` class, and refactor the enum constants and values to create 10 realistic pet owner names
++
+[source,java]
+.PetOwners_persona.java
+----
+@RequiredArgsConstructor
+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");
+
+ ...
+}
+----
+
+* create new `.pdf` files each pet owner, to store a "veterinary clinic service agreement" for each.
++
+The solution already provides these `.pdf` files, so you might want to borrow them.
+
+* locate the `DomainAppDemo` class and update:
++
+[source,java]
+.DomainAppDemo.java
+----
+@Override
+protected void execute(final ExecutionContext ec) {
+ ec.executeChildren(this, moduleWithFixturesService.getTeardownFixture());
+ ec.executeChild(this, new PetOwner_persona.PersistAll()); // <.>
+}
+----
+<.> change to set up ``PetOwner``s (rather than ``SimpleObject``s, as before)
+
+
+Test that you can quickly create a whole set of `PetOwner` objects
+from the "Prototyping" menu:
+* "Prototyping > Run Fixture Script" and select "Domain App Demo", or
+* "Prototyping > Recreate Objects and Return First".
diff --git a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
index a7b0d399fb..d51996df78 100644
--- a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
@@ -151,7 +151,6 @@ Comparing `PetOwner` as it is currently defined with our domain model, there are
* we still need to add in a `telephoneNumber` property, and an `emailAddress` property
-
We'll make these changes in this exercise.
=== Solution
@@ -181,22 +180,119 @@ 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
-Add the `telephoneNumber` property:
+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;
+----
+<.> This places the property in a new "contact" fieldset group; we'll define that below
-Add the `emailAddress` property:
+* 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 "Reload Changed Classes" menu item.
+
+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
-TODO: ideas for future steps:
-- introduce a meta-annotation
-- introduce a custom type for these
+[source,bash]
+----
+git checkout tags/03-04-list-new-properties-of-PetOwner
+mvn clean install
+mvn -pl spring-boot:run
+----
+=== Tasks
+* Locate the `PetOwner.columnOrder.txt` and make these changes:
++
+[source,text]
+.PetOwner.columnOrder.txt
+----
+name
+telephoneNumber
+emailAddress
+lastVisit
+----
+
+Confirm that "Pet Owners > List" 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 "Reload Changed Classes" menu item.
+
+You can learn more about layout files xref:userguide::ui-layout-and-hints.adoc[here].
+
+
+
+[#exercise-3-5-run-default-fixture-at-startup]
+== Ex 3.5: Run default fixture at startup
+
+Prototyping involves a lot of iteration, meaning make a change, re-run the app, in the app re-create some example objects, inspect the result.
+
+One way to create objects is from the Prototyping menu; you might already have been doing this.
+
+xxx
+
+=== Solution
+
+[source,bash]
+----
+git checkout tags/03-04-list-new-properties-of-PetOwner
+mvn clean install
+mvn -pl spring-boot:run
+----
+
+=== Tasks
+
+----
+----
+
+----
+----
+
+----
+----
+*UP TO HERE*
[#exercise-3-4-modify-the-menu-action-to-create-petowners]
== Ex 3.4: Modify the menu action to create PetOwners
diff --git a/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc b/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
new file mode 100644
index 0000000000..a669ffc86b
--- /dev/null
+++ b/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
@@ -0,0 +1,4 @@
+
+TODO: ideas for future steps:
+- introduce a meta-annotation
+- introduce a custom type for these
(causeway) 22/29: CAUSEWAY-2873: cleans up the _todo.adoc for petclinic
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 3c3bb6b937f0ba02a98a5e2dd4b8097dce66d378
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Mon May 27 17:54:05 2024 +0100
CAUSEWAY-2873: cleans up the _todo.adoc for petclinic
---
.../modules/petclinic/pages/100-todo.adoc | 121 ---------------------
.../tutorials/modules/petclinic/pages/_todo.adoc | 14 +++
2 files changed, 14 insertions(+), 121 deletions(-)
diff --git a/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc b/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
deleted file mode 100644
index 13156e85d2..0000000000
--- a/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
+++ /dev/null
@@ -1,121 +0,0 @@
-
-TODO: ideas for future steps:
-* more user-friendly error message if hit duplicate (enter two pet owners with same name)
-
-
-validate pet name is unique within Pet
-
-
-refactor addPet to be an inline-mixin.
-
-need a disable and a hide action
-
-
-update home page, show upcoming appointments
-
-
-visit
-- need some words about adding VisitRepository
-
-cf : we no longer have a PetRepository, but adapt...
-
-
-[#exercise-4-2-add-petrepository]
-== Ex 4.2: Add PetRepository
-
-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.
-
-
-
-
-
-PetOwner#daysSinceLastVisit could be made into a mixin - eg if these marketing analytics were the responsibility of some other module.
-
-
-hidden properties
-
-
-mixins
-
-[source,java]
-.PetOwner_pets.java
-----
-import lombok.RequiredArgsConstructor;
-
-@Collection // <.>
-@CollectionLayout(defaultView = "table")
-@RequiredArgsConstructor // <.>
-public class PetOwner_pets { // <.>
-
- private final PetOwner petOwner; // <.>
-
- public List<Pet> coll() {
- return petRepository.findByPetOwner(petOwner); // <.>
- }
-
- @Inject PetRepository petRepository; // <5>
-}
-----
-<.> 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`.
-
-
-
-
-[#exercise-4-5-digression-clean-up-casing-of-database-schema]
-== Ex 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
-
-[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.
-
diff --git a/antora/components/tutorials/modules/petclinic/pages/_todo.adoc b/antora/components/tutorials/modules/petclinic/pages/_todo.adoc
new file mode 100644
index 0000000000..d2f920205b
--- /dev/null
+++ b/antora/components/tutorials/modules/petclinic/pages/_todo.adoc
@@ -0,0 +1,14 @@
+= Ideas for the future...
+
+: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 [...]
+
+
+* more user-friendly error message if hit duplicate (enter two pet owners with same name)
+
+* refactor addPet to be an inline-mixin.
+
+* add an disable and a hide action
+
+* PetOwner#daysSinceLastVisit could be made into a mixin
+** eg if these marketing analytics were the responsibility of some other module.
+
(causeway) 28/29: removes reference to deleted 'tooling'
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit fc2297847e36ab8a2a6d5b20c9019afd1b3a5c4c
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Tue May 28 09:36:36 2024 +0100
removes reference to deleted 'tooling'
---
.../components/docs/modules/ROOT/pages/about.adoc | 1 -
.../ROOT/partials/module-nav/framework.adoc | 1 -
antora/playbooks/site.orig.yml | 305 +++++++++++++++++++++
3 files changed, 305 insertions(+), 2 deletions(-)
diff --git a/antora/components/docs/modules/ROOT/pages/about.adoc b/antora/components/docs/modules/ROOT/pages/about.adoc
index 90543c0f67..a1ae40e7c8 100644
--- a/antora/components/docs/modules/ROOT/pages/about.adoc
+++ b/antora/components/docs/modules/ROOT/pages/about.adoc
@@ -149,7 +149,6 @@ _Interim Builds_
_Process_
* *xref:comguide:ROOT:about.adoc[Committers' Guide]*
-* xref:tooling:ROOT:about.adoc[Tooling]
_Automated Analysis_
diff --git a/antora/components/docs/modules/ROOT/partials/module-nav/framework.adoc b/antora/components/docs/modules/ROOT/partials/module-nav/framework.adoc
index a76d4773ca..3fe2d0c21f 100644
--- a/antora/components/docs/modules/ROOT/partials/module-nav/framework.adoc
+++ b/antora/components/docs/modules/ROOT/partials/module-nav/framework.adoc
@@ -7,7 +7,6 @@
** Process
*** xref:comguide:ROOT:about.adoc[Committers' Guide]
-*** xref:tooling:ROOT:about.adoc[Tooling]
** Automated Analysis
*** link:https://sonarcloud.io/dashboard?id=apache_causeway[SonarCloud.io]
diff --git a/antora/playbooks/site.orig.yml b/antora/playbooks/site.orig.yml
new file mode 100644
index 0000000000..e9503d84d7
--- /dev/null
+++ b/antora/playbooks/site.orig.yml
@@ -0,0 +1,305 @@
+# 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.
+
+site:
+ title: causeway
+ url: https://causeway.apache.org/
+ start_page: docs:ROOT:about.adoc
+
+urls:
+ latest_version_segment: latest
+
+content:
+ sources:
+
+# antora
+ - url: .
+ start_path: antora/components/docs # docs
+ branches: HEAD
+ - url: .
+ start_path: antora/components/comguide # comguide
+ branches: HEAD
+ - url: .
+ start_path: antora/components/conguide # conguide
+ branches: HEAD
+ - url: .
+ start_path: antora/components/relnotes # relnotes
+ branches: HEAD
+ - url: .
+ start_path: antora/components/setupguide # setupguide
+ branches: HEAD
+ - url: .
+ start_path: antora/components/refguide # refguide
+ branches: HEAD
+ - url: .
+ start_path: antora/components/refguide-index # refguide
+ branches: HEAD
+ - url: .
+ start_path: antora/components/userguide # userguide
+ branches: HEAD
+
+# api
+ - url: .
+ start_path: api/schema/src/main/adoc # refguide
+ branches: HEAD
+
+# core
+ - url: .
+ start_path: core/adoc # core (design docs)
+ branches: HEAD
+ - url: .
+ start_path: core/codegen-bytebuddy/src/main/adoc # core
+ branches: HEAD
+ - url: .
+ start_path: commons/src/main/adoc # core
+ branches: HEAD
+ - url: .
+ start_path: core/config/src/main/adoc # refguide
+ branches: HEAD
+ - url: .
+ start_path: core/interaction/src/main/adoc # core
+ branches: HEAD
+ - url: .
+ start_path: core/internaltestsupport/src/main/adoc # core
+ branches: HEAD
+ - url: .
+ start_path: core/metamodel/src/main/adoc # core
+ branches: HEAD
+ - url: .
+ start_path: core/runtime/src/main/adoc # core
+ branches: HEAD
+ - url: .
+ start_path: core/runtimeservices/src/main/adoc # core
+ branches: HEAD
+ - url: .
+ start_path: core/security/src/main/adoc # security
+ branches: HEAD
+ - url: .
+ start_path: core/transaction/src/main/adoc # core
+ branches: HEAD
+ - url: .
+ start_path: core/webapp/src/main/adoc # core
+ branches: HEAD
+
+# examples
+ - url: .
+ start_path: antora/components/tutorials # tutorials
+ branches: HEAD
+
+# extensions
+ - url: .
+ start_path: extensions/adoc # extensions
+ branches: HEAD
+
+ - url: .
+ start_path: extensions/core/commandlog/adoc # userguide
+ branches: HEAD
+ - url: .
+ start_path: extensions/core/docgen/adoc # userguide
+ branches: HEAD
+ - url: .
+ start_path: extensions/core/excel/adoc # userguide
+ branches: HEAD
+ - url: .
+ start_path: extensions/core/executionlog/adoc # userguide
+ branches: HEAD
+ - url: .
+ start_path: extensions/core/executionoutbox/adoc # userguide
+ branches: HEAD
+ - url: .
+ start_path: extensions/core/executionrepublisher/adoc # userguide
+ branches: HEAD
+ - url: .
+ start_path: extensions/core/flyway/adoc # userguide
+ branches: HEAD
+ - url: .
+ start_path: extensions/core/layoutloaders/adoc # userguide
+ branches: HEAD
+
+ - url: .
+ start_path: extensions/security/audittrail/adoc # security
+ branches: HEAD
+ - url: .
+ start_path: extensions/security/secman/adoc # security
+ branches: HEAD
+ - url: .
+ start_path: extensions/security/sessionlog/adoc # security
+ branches: HEAD
+ - url: .
+ start_path: extensions/security/spring-oauth2/src/main/adoc # security
+ branches: HEAD
+
+ - url: .
+ start_path: extensions/vro/cors/adoc # vro
+ branches: HEAD
+
+ - url: .
+ start_path: extensions/vw/tabular/adoc # vw
+ branches: HEAD
+ - url: .
+ start_path: extensions/vw/fullcalendar/adoc # vw
+ branches: HEAD
+ - url: .
+ start_path: extensions/vw/pdfjs/adoc # vw
+ branches: HEAD
+ - url: .
+ start_path: extensions/vw/sse/adoc # vw
+ branches: HEAD
+
+# incubator
+ - url: .
+ start_path: incubator/adoc # incubator
+ branches: HEAD
+# - url: .
+# start_path: incubator/clients/kroviz/adoc # incubator
+# branches: HEAD
+# - url: .
+# start_path: incubator/extensions/core/commandreplay/adoc # incubator
+# branches: HEAD
+
+# mavendeps
+ - url: .
+ start_path: mavendeps/adoc # starters
+ branches: HEAD
+
+# persistence
+ - url: .
+ start_path: persistence/jpa/adoc # pjpa
+ branches: HEAD
+ - url: .
+ start_path: persistence/jdo/adoc # pjdo
+ branches: HEAD
+
+# regression tests
+ - url: .
+ start_path: regressiontests/adoc # regressiontests
+ branches: HEAD
+
+# security
+ - url: .
+ start_path: security/adoc # security
+ branches: HEAD
+ - url: .
+ start_path: security/bypass/src/main/adoc # security
+ branches: HEAD
+ - url: .
+ start_path: security/simple/src/main/adoc # security
+ branches: HEAD
+ - url: .
+ start_path: security/shiro/src/main/adoc # security
+ branches: HEAD
+ - url: .
+ start_path: security/keycloak/src/main/adoc # security
+ branches: HEAD
+ - url: .
+ start_path: security/spring/src/main/adoc # security
+ branches: HEAD
+
+# starters
+ - url: .
+ start_path: starters/adoc # toc
+ branches: HEAD
+
+
+# testing
+ - url: .
+ start_path: testing/adoc # testing
+ branches: HEAD
+ - url: .
+ start_path: testing/archtestsupport/adoc # testing
+ branches: HEAD
+ - url: .
+ start_path: testing/fakedata/adoc # testing
+ branches: HEAD
+ - url: .
+ start_path: testing/fixtures/adoc # testing
+ branches: HEAD
+ - url: .
+ start_path: testing/h2console/adoc # testing
+ branches: HEAD
+ - url: .
+ start_path: testing/hsqldbmgr/adoc # testing
+ branches: HEAD
+ - url: .
+ start_path: testing/integtestsupport/adoc # testing
+ branches: HEAD
+ - url: .
+ start_path: testing/specsupport/adoc # testing
+ branches: HEAD
+ - url: .
+ start_path: testing/unittestsupport/adoc # testing
+ branches: HEAD
+
+# valuetypes
+ - url: .
+ start_path: valuetypes/adoc # valuetypes
+ branches: HEAD
+ - url: .
+ start_path: valuetypes/asciidoc/adoc # valuetypes
+ branches: HEAD
+ - url: .
+ start_path: valuetypes/jodatime/adoc # valuetypes
+ branches: HEAD
+ - url: .
+ start_path: valuetypes/markdown/adoc # valuetypes
+ branches: HEAD
+ - url: .
+ start_path: valuetypes/vega/adoc # valuetypes
+ branches: HEAD
+
+# viewers
+ - url: .
+ start_path: viewers/graphql/adoc
+ branches: HEAD
+ - url: .
+ start_path: viewers/restfulobjects/adoc # vro
+ branches: HEAD
+ - url: .
+ start_path: viewers/wicket/adoc # vw
+ branches: HEAD
+
+
+ui:
+ bundle:
+# url: ../../causeway-antora/build/ui-bundle.zip
+ url: https://github.com/apache/causeway-antora/blob/master/build/ui-bundle.zip?raw=true
+ snapshot: true
+ supplemental_files: antora/supplemental-ui
+
+asciidoc:
+ # npm i @antora/cli@^3.1.1 @antora/site-generator-default@^3.1.1 asciidoctor-kroki
+ extensions:
+ - asciidoctor-kroki
+ - ./antora/lib/lorem
+ - ./antora/lib/jira-issue
+ attributes:
+ experimental: ""
+ idprefix: ""
+ idseparator: "-"
+ # kroki
+ # for more options see https://github.com/Mogztter/asciidoctor-kroki
+ kroki-default-format: svg # (default)
+ kroki-default-options: interactive # only available for SVG
+ kroki-fetch-diagram: true
+ kroki-server-url: https://kroki.io # (default)
+ page-causewayprevv2: "2.0.0"
+ page-causewayprevv3: "3.0.0"
+ page-causewayrelv2: "2.0.1"
+ page-causewayrelv3: "3.0.1"
+
+output:
+ dir: antora/target/site
(causeway) 13/29: CAUSEWAY-2873: 04-01
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 2bcf0f11fa3f66a73a396f3e830e4ad3ec07f671
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sun May 26 11:31:18 2024 +0100
CAUSEWAY-2873: 04-01
---
.../petclinic/images/04-01/h2-console-prompt.png | Bin 0 -> 14517 bytes
.../04-01/pet-and-petowner-tables-created.png | Bin 0 -> 7712 bytes
.../modules/petclinic/pages/040-pet-entity.adoc | 27 +++++++++++++--------
3 files changed, 17 insertions(+), 10 deletions(-)
diff --git a/antora/components/tutorials/modules/petclinic/images/04-01/h2-console-prompt.png b/antora/components/tutorials/modules/petclinic/images/04-01/h2-console-prompt.png
new file mode 100644
index 0000000000..d1120cd20b
Binary files /dev/null and b/antora/components/tutorials/modules/petclinic/images/04-01/h2-console-prompt.png differ
diff --git a/antora/components/tutorials/modules/petclinic/images/04-01/pet-and-petowner-tables-created.png b/antora/components/tutorials/modules/petclinic/images/04-01/pet-and-petowner-tables-created.png
new file mode 100644
index 0000000000..9693f2e47d
Binary files /dev/null and b/antora/components/tutorials/modules/petclinic/images/04-01/pet-and-petowner-tables-created.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 7de8e8b96e..b8fa0c870c 100644
--- a/antora/components/tutorials/modules/petclinic/pages/040-pet-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/040-pet-entity.adoc
@@ -26,8 +26,6 @@ mvn clean install
mvn -pl spring-boot:run
----
-
-
=== Tasks
* create a meta-annotation `@PetName` for the Pet's name:
@@ -52,13 +50,13 @@ public @interface PetName {
----
@Entity
@Table(
- schema="pets",
- uniqueConstraints = {
- @UniqueConstraint(name = "Pet__owner_name__UNQ", columnNames = {"owner_id, name"})
- }
+ schema= PetOwnerModule.SCHEMA,
+ uniqueConstraints = {
+ @UniqueConstraint(name = "Pet__owner_name__UNQ", columnNames = {"owner_id, name"})
+ }
)
@EntityListeners(CausewayEntityListener.class)
-@Named("pets.Pet")
+@Named(PetOwnerModule.NAMESPACE + ".Pet")
@DomainObject(entityChangePublishing = Publishing.ENABLED)
@DomainObjectLayout()
@NoArgsConstructor(access = AccessLevel.PUBLIC)
@@ -93,7 +91,7 @@ public class Pet implements Comparable<Pet> {
private PetOwner petOwner;
@PetName
- @Column(name = "name", length = FirstName.MAX_LEN, nullable = false)
+ @Column(name = "name", length = PetName.MAX_LEN, nullable = false)
@Getter @Setter
@PropertyLayout(fieldSetId = "name", sequence = "2")
private String name;
@@ -106,11 +104,20 @@ public class Pet implements Comparable<Pet> {
public int compareTo(final Pet other) {
return comparator.compare(this, other);
}
-
}
----
-Run the application, and confirm that the table is created correctly using menu:Prototyping[H2 Console].
+Run the application, and confirm that the application starts correctly.
+
+Login as `secman-admin` login (password: `pass`) to access the menu:Prototyping[H2 Console]:
+
+image::04-01/h2-console-prompt.png[]
+
+There is no password.
+
+Confirm the `Pet` table is created correctly:
+
+image::04-01/pet-and-petowner-tables-created.png[]
(causeway) 08/29: CAUSEWAY-2873: removes old exercise
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 4ea10043b3a1eb5030522aac853c109ef40d5554
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sat May 25 14:48:11 2024 +0100
CAUSEWAY-2873: removes old exercise
---
.../petclinic/pages/030-petowner-entity.adoc | 144 ---------------------
.../modules/petclinic/pages/100-todo.adoc | 5 +
2 files changed, 5 insertions(+), 144 deletions(-)
diff --git a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
index 529472a39d..f0b01273b5 100644
--- a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
@@ -649,150 +649,6 @@ daysSinceLastVisit
----
-* Hide the `lastName` and `firstName` properties, using `@Property(hidden=...)`.
-We can also remove the `@PropertyLayout` annotation.
-+
-[source,java]
-.PetOwner.java
-----
-@LastName
-@Column(length = LastName.MAX_LEN, nullable = false)
-@Getter @Setter @ToString.Include
-@Property(hidden = Where.EVERYWHERE)
-private String lastName;
-
-@FirstName
-@Column(length = FirstName.MAX_LEN, nullable = true)
-@Getter @Setter @ToString.Include
-@Property(hidden = Where.EVERYWHERE)
-private String firstName;
-----
-
-
-* Update the `PetOwner#updateName` to associate with the new `name` property:
-+
-[source,xml]
-----
-@ActionLayout(associateWith = "name",)
-public PetOwner updateName( ... ) {}
-----
-
-
-Run the application and check that it behaves as you expect.
-
-However, if you now try to build the app (`mvn clean install`) then you'll hit test errors, because we have changed the visibility of the `lastName` and `firstName` properties.
-
-We will be looking at tests later on, so if you want to just comment out the failing tests, then do that.
-Alternatively, here are the changes that need to be made:
-
-* update the `PetOwner_IntegTest#name` nested static test class:
-+
-[source,java]
-.PetOwner_IntegTest.java
-----
-@Nested
-public static class name extends PetOwner_IntegTest {
-
- @Test
- public void accessible() {
- // when
- final String name = wrap(petOwner).getName(); // <.>
-
- // then
- assertThat(name).isEqualTo(petOwner.getLastName());
- }
-
- // <.>
-}
-----
-<.> change this line from `getLastName()` to `getName()`
-<.> delete the 'editable' test
-
-* add a new `PetOwner_IntegTest#lastName` nested static test class to check that the `lastName` property can no longer be viewed:
-+
-[source,java]
-.PetOwner_IntegTest.java
-----
-@Nested
-public static class lastName extends PetOwner_IntegTest {
-
- @Test
- public void not_accessible() {
- // expect
- assertThrows(HiddenException.class, ()->{
-
- // when
- wrap(petOwner).getLastName();
- });
- }
-}
-----
-+
-This asserts that the `lastName` property cannot be viewed.
-
-* add a new `PetOwner_IntegTest#firstName` nested static test class to check that the `firstName` property can no longer be viewed.
-+
-[source,java]
-.PetOwner_IntegTest.java
-----
-@Nested
-public static class firstName extends PetOwner_IntegTest {
-
- @Test
- public void not_accessible() {
- // expect
- assertThrows(HiddenException.class, ()->{
-
- // when
- wrap(petOwner).getFirstName();
- });
- }
-}
-----
-
-* update the `PetOwner_IntegTest#updateName` nested static test class, specifically the assertion:
-+
-[source,java]
-.PetOwner_IntegTest.java
-----
-@Nested
-public static class updateName extends PetOwner_IntegTest {
-
-
- @Test
- public void can_be_updated_directly() {
-
- // when
- wrap(petOwner).updateName("McAdam", "Adam"); // <.>
- transactionService.flushTransaction();
-
- // then
- assertThat(petOwner.getLastName()).isEqualTo("McAdam"); // <.>
- assertThat(petOwner.getFirstName()).isEqualTo("Adam"); // <2>
- }
- //...
-}
-----
-<.> provide both `lastName` and `firstName` parameters
-<.> assert on both properties.
-Note that the `petOwner` object cannot be "wrapped".
-
-In case you are wondering, the wrap method is a call to xref:refguide:applib:index/services/wrapper/WrapperFactory.adoc[WrapperFactory], which provides a proxy to the object.
-This proxy emulates the UI, in this case enforcing the "hidden" rule by throwing an exception if it would not be visible.
-For this test, we _want_ to peek under the covers to check the direct state of the entity, therefore we don't wrap the object.
-
-* also update the `Smoke_IntegTest`:
-+
-[source,java]
-.Smoke_IntegTest.java
-----
-...
-assertThat(wrap(fred).getName()).isEqualTo("Freddy"); // <.>
-...
-----
-<.> previously was "wrap(fred).getLastName().
-
-
[#exercise-3-8-add-other-properties-for-petowner]
== Ex 3.8: Add other properties for PetOwner
diff --git a/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc b/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
index 66bd2a3312..6ebd2b2b7f 100644
--- a/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
@@ -8,3 +8,8 @@ add validation for telephone number and email address.
PetOwner#daysSinceLastVisit could be made into a mixin - eg if these marketing analytics were the responsibility of some other module.
+
+
+
+hidden properties
+
(causeway) 19/29: CAUSEWAY-2873: 07-01
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit ca983b39152a5f06ead399f410aa4d934b56dbe6
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sun May 26 22:49:06 2024 +0100
CAUSEWAY-2873: 07-01
---
.../modules/petclinic/pages/070-modularity.adoc | 153 ++++++++-------------
.../modules/petclinic/pages/100-todo.adoc | 2 +
2 files changed, 62 insertions(+), 93 deletions(-)
diff --git a/antora/components/tutorials/modules/petclinic/pages/070-modularity.adoc b/antora/components/tutorials/modules/petclinic/pages/070-modularity.adoc
index 793a6aad36..fa827459db 100644
--- a/antora/components/tutorials/modules/petclinic/pages/070-modularity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/070-modularity.adoc
@@ -8,29 +8,28 @@ If every class potentially can depend on any other class, we'll end up with a "b
Instead, we need to ensure that the dependency graph between packages remains acyclic.
The framework provides two main tools:
-* the first we've already seen is mixins.
+* the first we've already seen: mixins.
+
These allow us to locate business logic in one module that "appears" to reside in another module.
Examples are the `visits` mixin collection and `bookVisit` mixin action that are both contributed by the `visits` module to the `Pet` entity in the `pets` module.
* the second is domain events.
+
-These we haven't yet seen, but provide a way for one module to react to (or to veto) actions performed in logic in another module.
+These provide a way for one module to react to (or to veto) actions performed in logic in another module.
-In this part of the tutorial we'll look at domain events.
+In this section we'll look at domain events.
[#exercise-7-1-refactor-petowners-delete-action]
== Ex 7.1: refactor PetOwner's delete action
-Currently the `delete` action for `PetOwner` is implemented as a mixin within the `Pet` package.
-That's a nice place for that functionality, because it can delete any `Pet`s for the `PetOwner` if any exist.
+Currently the `delete` action for `PetOwner` is broken: although the owner's ``Pet``s are automatically deleted when the `PetOwner` is itself deleted, if there are any ``Visit``s then the foreign key in the database will prevent deletion.
-However, we also have added `Visit`, which has the same issue: we cannot delete a `Pet` if there are associated ``Visit``s.
-And, in fact, we don't want to allow a `PetOwner` and their ``Pet``s from being deleted if there are ``Visit``s in the database; they might not have paid!
+In one sense this is good: we probably don't want to allow a `PetOwner` and their ``Pet``s from being deleted if there are ``Visit``s in the database; they might not have paid!
+However, we ought to have the business logic in the domain layer rather than rely on the database's foreign key.
-In this exercise we will move the responsibility to delete an action back to `PetOwner`, and then use subscribers for both `Pet` and `Visit` to cascade delete or to veto the action respectively if there are related objects.
+In this exercise we'll use domain events to cascade delete or to veto the action respectively if there are related objects.
@@ -43,116 +42,84 @@ mvn clean install
mvn -pl spring-boot:run
----
-To test this out:
-
-* try deleting a `PetOwner` where none of their ``Pet``s have any ``Visit``s; the action should succeed, and the `PetOwner` and the ``Pet``s should all be deleted.
-
-* now book a `Visit` for a `Pet`, then navigate back to the parent `PetOwner` and attempt to delete it.
-This time the action should be vetoed, because of that `Visit`.
-
-
=== Tasks
-* in `PetOwner_delete` remove the code that deletes the ``Pet``s.
-In its place, define a subclass of xref:refguide:applib-classes:events.adoc#domain-event-classes[ActionDomainEvent] as a nested class of the mixin, and reference in the xref:refguide:applib:index/annotation/Action.adoc#domainEvent[@Action#domainEvent] attribute.
+* (optional) confirm that although it's not possible to delete a `PetOwner` if there are corresponding ``Visit``s, the error we get back is a database exception
+
+* in `PetOwner`, modify the `delete` action so that it emits a specific domain event type.
+
[source,java]
-.PetOwner_delete.java
+.PetOwner.java
----
-@Action(
- domainEvent = PetOwner_delete.ActionEvent.class, // <.>
- semantics = SemanticsOf.NON_IDEMPOTENT_ARE_YOU_SURE,
- commandPublishing = Publishing.ENABLED,
- executionPublishing = Publishing.ENABLED
-)
-@ActionLayout(
- associateWith = "name", position = ActionLayout.Position.PANEL,
- describedAs = "Deletes this object from the persistent datastore")
-@RequiredArgsConstructor
-public class PetOwner_delete {
-
- public static class ActionEvent // <.>
- extends ActionDomainEvent<PetOwner_delete>{}
-
- private final PetOwner petOwner;
-
- public void act() {
- repositoryService.remove(petOwner);
- return;
- }
-
- @Inject RepositoryService repositoryService;
-}
+ public static class DeleteActionDomainEvent
+ extends org.apache.causeway.applib.events.domain.ActionDomainEvent<PetOwner> {} // <.>
+
+ @Action(
+ semantics = NON_IDEMPOTENT_ARE_YOU_SURE,
+ domainEvent = DeleteActionDomainEvent.class // <.>
+ )
+ @ActionLayout(
+ describedAs = "Deletes this object from the persistent datastore")
+ public void delete() { ... }
----
<.> specifies the domain event to emit when the action is called
<.> declares the action event (as a subclass of the framework's xref:refguide:applib-classes:events.adoc#domain-event-classes[ActionDomainEvent]).
++
+NOTE: in fact, domain events are always emitted; but by default a generic `ActionDomainEvent` is used rather than a specific subclass.
* create a subscriber in the `pets` package to delete all ``Pet``s when the `PetOwner_delete` action is invoked:
+
[source,java]
-.PetOwnerForPetsSubscriber.java
+.PetOwner_delete_subscriber.java
----
-@Service
-public class PetOwnerForPetsSubscriber {
-
- @EventListener(PetOwner_delete.ActionEvent.class)
- public void on(PetOwner_delete.ActionEvent ev) {
- switch(ev.getEventPhase()) {
- case EXECUTING: // <.>
- PetOwner petOwner = ev.getSubject(); // <.>
- List<Pet> pets = petRepository.findByPetOwner(petOwner);
- pets.forEach(repositoryService::remove);
+@Component
+public class PetOwner_delete_subscriber {
+
+ @EventListener(PetOwner.DeleteActionDomainEvent.class) // <.>
+ void on(PetOwner.DeleteActionDomainEvent event) { // <1>
+ PetOwner subject = event.getSubject(); // <.>
+ switch (event.getEventPhase()) { // <.>
+ case HIDE:
break;
- }
- }
-
- @Inject PetRepository petRepository;
- @Inject RepositoryService repositoryService;
-}
-----
-<.> events are emitted at different phases.
-The `EXECUTING` phase is fired before the delete action itself is fired, so is the ideal place for us to perform the cascade delete.
-<.> is the mixee of the mixin that is emitting the event.
-
-* create a subscriber in the `visits` module to veto the `PetOwner_delete` if there are any `Pet`s of the `PetOwner` with at least one `Visit`:
-+
-[source,java]
-.PetOwnerForVisitsSubscriber.java
-----
-@Service
-public class PetOwnerForVisitsSubscriber {
-
- @EventListener(PetOwner_delete.ActionEvent.class)
- public void on(PetOwner_delete.ActionEvent ev) {
- switch(ev.getEventPhase()) {
- case DISABLE:
- PetOwner petOwner = ev.getSubject();
- List<Pet> pets = petRepository.findByPetOwner(petOwner);
- for (Pet pet : pets) {
- List<Visit> visits = visitRepository.findByPetOrderByVisitAtDesc(pet);
- int numVisits = visits.size();
- if(numVisits > 0) {
- ev.disable(String.format("%s has %d visit%s",
- titleService.titleOf(pet),
- numVisits,
- numVisits != 1 ? "s" : ""));
- }
+ case DISABLE: // <.>
+ List<Visit> visits = visitRepository.findByPetOwner(subject);
+ if (!visits.isEmpty()) {
+ event.veto("This owner has %d visit%s", visits.size(), (visits.size() == 1 ? "" : "s"));
}
break;
+ case VALIDATE:
+ break;
+ case EXECUTING:
+ break;
+ case EXECUTED:
+ break;
}
}
- @Inject TitleService titleService;
@Inject VisitRepository visitRepository;
- @Inject PetRepository petRepository;
}
----
+<.> subscribes to the event using Spring `@EventListener`
+<.> returns the effective originator of the event.
+This works for both regular actions and mixin actions
+<.> the subscriber is called multiple times, for the various phases of the execution lifecycle; more on this below
+<.> if there are any ``Visit``s for this pet owner, then veto the interaction.
+In the user interface, the "delete" button will be disabled, that is greyed out.
+The returned string is used as the tooltip to explain _why_ the button is disabled.
+The event lifecycle allows subscribers to veto (in other words, specify preconditions) in three different ways:
-=== Optional Exercise
-
+* hide - will hide the action's button in the UI completely
+* disable - will disable (grey) out the action's button
+* validate - will prevent the button from being pressed.
+this case applies when validating the action arguments.
-Improve the implementation of `PetOwnerForVisitsSubscriber` so that it performs only a single database query to find if there are any ``Visit`` for the `PetOwner`.
+If the action is not vetored, then the subscriber is also possible to perform additional steps:
+* executing - the action is about to execute.
+* executed - the action is just execute
+For example, if the business rule had instead been to simply delete all ``Visit``s, then this could have been implemented in the "executing" phase.
+TIP: it's also worth knowing that these events are fired for properties and collections as well as actions.
+Therefore subscribers can substantially dictate what is accessible for any given domain object.
diff --git a/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc b/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
index 5ce9d43c8d..13156e85d2 100644
--- a/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
@@ -8,6 +8,8 @@ validate pet name is unique within Pet
refactor addPet to be an inline-mixin.
+need a disable and a hide action
+
update home page, show upcoming appointments
(causeway) 14/29: CAUSEWAY-2873: 04-04
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 0fd7818724368bd0691a407f390dfdd8f50150ee
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sun May 26 13:15:52 2024 +0100
CAUSEWAY-2873: 04-04
---
.../petclinic/pages/030-petowner-entity.adoc | 35 ++
.../modules/petclinic/pages/040-pet-entity.adoc | 497 ++++++++++-----------
.../modules/petclinic/pages/100-todo.adoc | 103 +++++
3 files changed, 367 insertions(+), 268 deletions(-)
diff --git a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
index 179a312512..4b899f7b39 100644
--- a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
@@ -1253,3 +1253,38 @@ As you might expect, these tags indicate where to render properties, collections
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 experts; 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]
+----
+git checkout tags/03-15-change-pet-owner-icon-png
+mvn clean install
+mvn -pl spring-boot:run
+----
+
+=== 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.
+
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 b8fa0c870c..1bb6b01974 100644
--- a/antora/components/tutorials/modules/petclinic/pages/040-pet-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/040-pet-entity.adoc
@@ -68,12 +68,11 @@ public class Pet implements Comparable<Pet> {
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", nullable = false)
@Getter @Setter
- @PropertyLayout(fieldSetId = "metadata", sequence = "1")
private Long id;
@Version
@Column(name = "version", nullable = false)
- @PropertyLayout(fieldSetId = "metadata", sequence = "999")
+ @PropertyLayout(fieldSetId = "metadata", sequence = "999") // <.>
@Getter @Setter
private long version;
@@ -86,14 +85,14 @@ public class Pet implements Comparable<Pet> {
@ManyToOne(optional = false)
@JoinColumn(name = "owner_id")
- @PropertyLayout(fieldSetId = "name", sequence = "1")
+ @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 = "name", sequence = "2")
+ @PropertyLayout(fieldSetId = "identity", sequence = "2") // <1>
private String name;
@@ -106,61 +105,161 @@ public class Pet implements Comparable<Pet> {
}
}
----
+<.> 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.
-Login as `secman-admin` login (password: `pass`) to access the menu:Prototyping[H2 Console]:
+Let's also confirm that JPA created the corresponding table automatically:
-image::04-01/h2-console-prompt.png[]
+* Login as `secman-admin` login (password: `pass`)
-There is no password.
+* Access the menu:Prototyping[H2 Console]:
++
+image::04-01/h2-console-prompt.png[]
-Confirm the `Pet` table is created correctly:
+* 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-petrepository]
-== Ex 4.2: Add PetRepository
+[#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.
-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
+git checkout tags/04-02-pet-ui-and-layout-semantics
mvn clean install
mvn -pl spring-boot:run
----
-
-
=== Tasks
-* create the `PetRepository`, extending Spring Data's `org.springframework.data.repository.Repository` interface:
+* annotate the `Pet#name` property with xref:refguide:applib:index/annotation/Title.adoc[@Title]:
+
[source,java]
-.PetRepository.java
+.Pet.java
----
-import org.springframework.data.repository.Repository;
-
-public interface PetRepository extends Repository<Pet, Long> {
-
- List<Pet> findByPetOwner(PetOwner petOwner);
-}
+@PetName
+@Title // <.>
+@Column(name = "name", length = PetName.MAX_LEN, nullable = false)
+@Getter @Setter
+@PropertyLayout(fieldSetId = "identity", sequence = "2")
+private String name;
----
+<.> added
-Confirm the application still runs
-
+* 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>
+----
+
+* 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
-In this next exercise we'll add the ``PetOwner``'s collection of ``Pet``s, using a xref:userguide:ROOT:mixins.adoc[mixin].
+According to our model, ``Pet``s are owned by ``PetOwner``s:
+
[plantuml]
----
@@ -192,98 +291,138 @@ package pets {
PetOwner *-r--> "0..*" Pet
----
+In this next exercise we'll add the ``PetOwner``'s collection of ``Pet``s.
+
+
=== Solution
[source,bash]
----
-git checkout tags/04-03-PetOwner-pets-mixin-collection
+git checkout tags/04-03-PetOwner-pets-collection
mvn clean install
mvn -pl spring-boot:run
----
=== Tasks
-* create the `PetOwner_pets` mixin class:
+* update the `PetOwner` class, add a `pets` collection:
+
[source,java]
----
-import org.apache.causeway.applib.annotation.Collection;
-import org.apache.causeway.applib.annotation.CollectionLayout;
-
-import lombok.RequiredArgsConstructor;
-
-@Collection // <.>
-@CollectionLayout(defaultView = "table")
-@RequiredArgsConstructor // <.>
-public class PetOwner_pets { // <.>
-
- private final PetOwner petOwner; // <.>
-
- public List<Pet> coll() {
- return petRepository.findByPetOwner(petOwner); // <.>
- }
-
- @Inject PetRepository petRepository; // <5>
-}
+@org.apache.causeway.applib.annotation.Collection
+@Getter
+@OneToMany(mappedBy = "petOwner", cascade = CascadeType.ALL, orphanRemoval = true)
+private Set<Pet> pets = new TreeSet<>();
----
-<.> 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:
+* update `PetOwner.layout.xml` file to position the `pets` collection on the right-hand side, above the `attachment` property:
+
[source,xml]
.PetOwner.layout.xml
----
-<bs:grid>
- <bs:row>
- <!--...-->
- </bs:row>
- <bs:row>
- <bs:col span="6">
- <!--...-->
- </bs:col>
- <bs:col span="6">
- <bs:tabGroup unreferencedCollections="true" collapseIfOne="false">
- <bs:tab name="Pets"> <!--.-->
- <bs:row>
- <bs:col span="12">
- <c:collection id="pets"/>
- </bs:col>
- </bs:row>
- </bs:tab>
- </bs:tabGroup>
- </bs:col>
- </bs:row>
-</bs:grid>
-----
-<.> define a tab on the right hand side to hold the `pets` collection.
-+
-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:
+<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
+#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]
+----
+git checkout tags/04-04-add-remove-pets
+mvn clean install
+mvn -pl spring-boot:run
+----
+
+=== 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:
+
-Run the application (or just reload the changed classes) and confirm the columns of the `pets` collection are correct.
+[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
+UP TO HERE.
+Recall that our
-[#exercise-4-4-add-pets-remaining-properties]
-== Ex 4.4: Add Pet's remaining properties
+
+
+[#exercise-4-5-add-pets-remaining-properties]
+== Ex 4.5: Add Pet's remaining properties
In this exercise we'll add the remaining properties for `Pet`.
@@ -322,7 +461,7 @@ Pet "*" -u-> PetSpecies
[source,bash]
----
-git checkout tags/04-04-pet-remaining-properties
+git checkout tags/04-03-pet-remaining-properties
mvn clean install
mvn -pl spring-boot:run
----
@@ -384,131 +523,15 @@ private String notes;
Run the application and use menu:Prototyping[H2 Console] to confirm the database schema for `Pet` is as expected.
-[#exercise-4-5-digression-clean-up-casing-of-database-schema]
-== Ex 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
-[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]
-== Ex 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-06-PetOwner-addPet-action
-mvn clean install
-mvn -pl spring-boot:run
-----
-=== Tasks
-* 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;
- }
+[#exercise-4-7-pet-title-and-dynamic-icons]
+== Ex 4.7: Pet title and dynamic icons
- @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);
-----
-
-* Now use a supporting xref:userguide:ROOT:business-rules.adoc#validity[validate] method to prevent two pets with the same name from being added:
-+
-[source,java]
-.PetOwner_addPet.java
-----
-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).
-
-
-* 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
-----
-public PetSpecies default1Act() {
- return PetSpecies.Dog;
-}
-----
-+
-Run the application once more to test.
-
-
-
-
-[#exercise-4-7-add-pets-ui-customisation]
-== Ex 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.
@@ -526,68 +549,6 @@ mvn -pl spring-boot:run
=== Tasks
-* Create a `Pet.layout.xml` file as follows:
-+
-[source,xml]
-.Pet.layout.xml
-----
-<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<bs: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/links https://causeway.apache.org/applib/layout/links/links.xsd https://causeway.apache.org/applib/layout/grid/bootstrap3 https://causeway.apache.org/applib/layout/grid/bootstrap3/bootstrap3.xsd" xmlns:bs="https://causeway.apache.org/applib/layout/grid/bootstrap3" xmlns:cpt="https://causeway.apache.org [...]
- <bs:row>
- <bs:col span="12" unreferencedActions="true">
- <cpt:domainObject bookmarking="AS_ROOT"/>
- </bs:col>
- </bs:row>
- <bs:row>
- <bs:col span="6">
- <bs:row>
- <bs:col span="12">
- <bs:tabGroup>
- <bs:tab name="General">
- <bs:row>
- <bs:col span="12">
- <cpt:fieldSet id="name"/>
- </bs:col>
- </bs:row>
- </bs:tab>
- <bs:tab name="Metadata">
- <bs:row>
- <bs:col span="12">
- <cpt:fieldSet name="Metadata" id="metadata"/>
- </bs:col>
- </bs:row>
- </bs:tab>
- <bs:tab name="Other">
- <bs:row>
- <bs:col span="12">
- <cpt:fieldSet name="Other" id="other" unreferencedProperties="true"/>
- </bs:col>
- </bs:row>
- </bs:tab>
- </bs:tabGroup>
- <cpt:fieldSet id="details" name="Details"/>
- <cpt:fieldSet id="notes" name="Notes"/>
- </bs:col>
- </bs:row>
- <bs:row>
- <bs:col span="12">
- </bs:col>
- </bs:row>
- </bs:col>
- <bs:col span="6">
- <bs:tabGroup unreferencedCollections="true"/>
- </bs:col>
- </bs:row>
-</bs: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:
diff --git a/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc b/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
index 760a44e164..a3377a3e3a 100644
--- a/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
@@ -3,9 +3,112 @@ TODO: ideas for future steps:
* more user-friendly error message if hit duplicate (enter two pet owners with same name)
+validate pet name is unique within Pet
+
+
+
+visit
+- need some words about adding VisitRepository
+
+cf : we no longer have a PetRepository, but adapt...
+
+
+[#exercise-4-2-add-petrepository]
+== Ex 4.2: Add PetRepository
+
+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.
+
+
+
+
PetOwner#daysSinceLastVisit could be made into a mixin - eg if these marketing analytics were the responsibility of some other module.
hidden properties
+
+mixins
+
+[source,java]
+.PetOwner_pets.java
+----
+import lombok.RequiredArgsConstructor;
+
+@Collection // <.>
+@CollectionLayout(defaultView = "table")
+@RequiredArgsConstructor // <.>
+public class PetOwner_pets { // <.>
+
+ private final PetOwner petOwner; // <.>
+
+ public List<Pet> coll() {
+ return petRepository.findByPetOwner(petOwner); // <.>
+ }
+
+ @Inject PetRepository petRepository; // <5>
+}
+----
+<.> 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`.
+
+
+
+
+[#exercise-4-5-digression-clean-up-casing-of-database-schema]
+== Ex 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
+
+[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.
+
(causeway) 06/29: CAUSEWAY-2873: completes 03-08
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 2e42e9446f5be24789139d7471e4de2390ff1702
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sat May 25 14:12:50 2024 +0100
CAUSEWAY-2873: completes 03-08
---
.../images/03-07/new-parameters-are-optional.png | Bin 0 -> 8602 bytes
...antuml-md5-41d7321e937c89d99e72b59cadcbe005.png | Bin 37003 -> 0 bytes
...antuml-md5-5c0ff86629ce9ff23e5d52305493d5c6.svg | 2 +-
...antuml-md5-7e47437805fde198d7791595949fcf17.png | Bin 5096 -> 4464 bytes
...antuml-md5-bc5fc3dd9c919d1677af89bec5416f29.svg | 1 -
.../petclinic/pages/030-petowner-entity.adoc | 102 +++++++++++++--------
.../modules/petclinic/pages/100-todo.adoc | 3 +
7 files changed, 70 insertions(+), 38 deletions(-)
diff --git a/antora/components/tutorials/modules/petclinic/images/03-07/new-parameters-are-optional.png b/antora/components/tutorials/modules/petclinic/images/03-07/new-parameters-are-optional.png
new file mode 100644
index 0000000000..06bedd5db0
Binary files /dev/null and b/antora/components/tutorials/modules/petclinic/images/03-07/new-parameters-are-optional.png differ
diff --git a/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-41d7321e937c89d99e72b59cadcbe005.png b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-41d7321e937c89d99e72b59cadcbe005.png
deleted file mode 100644
index 9b948f4bae..0000000000
Binary files a/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-41d7321e937c89d99e72b59cadcbe005.png and /dev/null differ
diff --git a/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-5c0ff86629ce9ff23e5d52305493d5c6.svg b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-5c0ff86629ce9ff23e5d52305493d5c6.svg
index 6f3b8ba884..50b30fc348 100644
--- a/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-5c0ff86629ce9ff23e5d52305493d5c6.svg
+++ b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-5c0ff86629ce9ff23e5d52305493d5c6.svg
@@ -1 +1 @@
-<?xml version='1.0' encoding='US-ASCII' standalone='no'?><svg contentStyleType='text/css' height='161px' preserveAspectRatio='none' style='width:141px;height:161px;background:#FFFFFF;' version='1.1' viewBox='0 0 141 161' width='141px' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns='http://www.w3.org/2000/svg' zoomAndPan='magnify'><defs/><g><g id='elem_Owner'><rect codeLine='4' fill='#F1F1F1' height='140.7144' id='Owner' rx='2.5' ry='2.5' style='stroke:#181818;stroke-width:0.5;' width=' [...]
\ No newline at end of file
+<?xml version='1.0' encoding='US-ASCII' standalone='no'?><svg contentStyleType='text/css' height='170px' preserveAspectRatio='none' style='width:136px;height:170px;background:#FFFFFF;' version='1.1' viewBox='0 0 136 170' width='136px' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns='http://www.w3.org/2000/svg' zoomAndPan='magnify'><defs/><g><g id='elem_Owner'><rect codeLine='4' fill='#F1F1F1' height='149.1055' id='Owner' rx='2.5' ry='2.5' style='stroke:#181818;stroke-width:0.5;' width=' [...]
\ No newline at end of file
diff --git a/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-7e47437805fde198d7791595949fcf17.png b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-7e47437805fde198d7791595949fcf17.png
index 12035b77b0..d4fdb061d9 100644
Binary files a/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-7e47437805fde198d7791595949fcf17.png and b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-7e47437805fde198d7791595949fcf17.png differ
diff --git a/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-bc5fc3dd9c919d1677af89bec5416f29.svg b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-bc5fc3dd9c919d1677af89bec5416f29.svg
deleted file mode 100644
index a9963efc83..0000000000
--- a/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-bc5fc3dd9c919d1677af89bec5416f29.svg
+++ /dev/null
@@ -1 +0,0 @@
-<?xml version='1.0' encoding='US-ASCII' standalone='no'?><svg contentStyleType='text/css' height='441px' preserveAspectRatio='none' style='width:520px;height:441px;background:#FFFFFF;' version='1.1' viewBox='0 0 520 441' width='520px' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns='http://www.w3.org/2000/svg' zoomAndPan='magnify'><defs/><g><g id='cluster_pets'><path d='M8.5,6 L38.5,6 A3.75,3.75 0 0 1 41,8.5 L48,28.9429 L282.5,28.9429 A2.5,2.5 0 0 1 285,31.4429 L285,431.5 A2.5,2.5 0 0 1 [...]
\ No newline at end of file
diff --git a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
index 4d46d3bc39..383149ad87 100644
--- a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
@@ -186,12 +186,13 @@ Now add the two new properties:
[source,java]
.PetOwner.java
----
-@Column(length = 40, nullable = true, name = "telephoneNumber")
+@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:
@@ -339,7 +340,7 @@ When you now run the application you should now find that there are 10 `PetOwner
[#exercise-3-6-update-home-page-to-show-PetOwners]
-== Ex 3.5: 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.
@@ -386,10 +387,7 @@ 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`:
-
-
-Instead
+We can change this by creating a file `HomePageViewModel#petOwners.columnOrder.txt`, alongside the `HomePageViewModel`.
[source,txt]
.HomePageViewModel#petOwners.columnOrder.txt
@@ -405,39 +403,29 @@ emailAddress
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.
+TIP: You can config your IDE so that any changes made to this file are picked up automatically without having to restart the app.
+If using IntelliJ for example, use "Reload Changed Classes" action.
-----
-----
-----
-----
-
-----
-----
+[#exercise-3-7-modify-the-menu-action-to-create-petowners]
+== Ex 3.7: Modify the menu action to create PetOwners
-TODO: change the home page to list the pet owners by default.
+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.
-*UP TO HERE*
-
-[#exercise-3-4-modify-the-menu-action-to-create-petowners]
-== Ex 3.4: Modify the menu action to create PetOwners
-
-If we want to create a new `PetOwner` and provide their `firstName`, at the moment it's a two stage process: create the `PetOwner` (using `PetOwners#create` action from the menu), then update their name (using the `updateName` action that we just looked at).
-
-In this exercise we'll simplify that workflow by allowing the `firstName` to optionally be specified during the initial create.
+In this exercise we'll simplify that workflow by allowing the additional details to optionally be specified during the create.
=== Solution
[source,bash]
----
-git checkout tags/03-04-modifies-PetOwners-create-action
+git checkout tags/03-07-modifies-PetOwners-create-action
mvn clean install
mvn -pl spring-boot:run
----
=== Tasks
-* update `Orders#create` action, so that the end user can specify a `firstName` when creating a new `PetOwner`:
+* update `PetOwners#create()` method, to allow the additional details to optionally be specified:
+
[source,java]
.PetOwners.java
@@ -445,30 +433,48 @@ mvn -pl spring-boot:run
@Action(semantics = SemanticsOf.NON_IDEMPOTENT)
@ActionLayout(promptStyle = PromptStyle.DIALOG_SIDEBAR)
public PetOwner create(
- @LastName final String lastName,
- @FirstName final String firstName) {
- return repositoryService.persist(PetOwner.withName(lastName, firstName));
+ @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);
+----
+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-6-prompt-styles]
-== Ex 3.6: Prompt styles
+[#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 `PetOwners#create`, and an object action, ie `PetOwner#updateName`.
+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]
----
-git checkout tags/03-06-prompt-styles
+git checkout tags/03-08-prompt-styles
mvn clean install
mvn -pl spring-boot:run
----
@@ -479,12 +485,36 @@ mvn -pl spring-boot:run
* 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` and confirm that the dialog is now shown as a modal prompt.
+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.
+
-Therefore remove the `@ActionLayout(promptStyle)` for `PetOwner#updateName` and confirm that prompt is still inline.
+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.
+
@@ -506,7 +536,7 @@ Remember to activate this new profile (`-Dspring.profiles.active=dev,custom`) an
Switch the defaults so that service actions prefer to use a sidebar dialog, while object actions would use a modal dialog:
+
[source,yaml]
-.application-custom.yaml
+.application-dev.yaml
----
causeway:
viewer:
@@ -516,14 +546,14 @@ causeway:
dialog-mode-for-menu: sidebar
----
-* Optional: now use `@ActionLayout(promptStyle=...)` to override these defaults.
+* 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-custom.yaml
+.application-dev.yaml
----
causeway:
viewer:
diff --git a/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc b/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
index a669ffc86b..b8603a2c3b 100644
--- a/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
@@ -2,3 +2,6 @@
TODO: ideas for future steps:
- introduce a meta-annotation
- introduce a custom type for these
+
+
+add validation for telephone number and email address.
(causeway) 09/29: CAUSEWAY-2873: 03-13
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 807b2f62fa15a9df7f074cf3ea5d988b0052382d
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sun May 26 09:41:50 2024 +0100
CAUSEWAY-2873: 03-13
---
.../petclinic/pages/030-petowner-entity.adoc | 466 +++++++++++++--------
.../modules/petclinic/pages/100-todo.adoc | 6 +-
2 files changed, 296 insertions(+), 176 deletions(-)
diff --git a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
index f0b01273b5..33c41617cd 100644
--- a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
@@ -611,7 +611,9 @@ Note that Apache Causeway allows services to be injected into entities (actually
@Property
@PropertyLayout(fieldSetId = LayoutConstants.FieldSetId.DETAILS, sequence = "3.1") // <.>
public long getDaysSinceLastVisit() {
- return Math.abs(ChronoUnit.DAYS.between(getLastVisit(), clockService.getClock().nowAsLocalDate()));
+ return getLastVisit() != null
+ ? ChronoUnit.DAYS.between(getLastVisit(), clockService.getClock().nowAsLocalDate())
+ : null;
}
----
<.> positioned just after the `lastVisit` property
@@ -650,34 +652,22 @@ daysSinceLastVisit
-[#exercise-3-8-add-other-properties-for-petowner]
-== Ex 3.8: Add other properties for PetOwner
+[#exercise-3-10-use-meta-annotations-to-reduce-duplication]
+== Ex 3.10: Use meta-annotations to reduce duplication
-Let's add the two remaining properties for `PetOwner`:
+There is some duplication between menu:PetOwners[create] action and the `PetOwner` class; both define a "name" parameter along with other optional parameters, "telephoneNumber, "emailAddress" and "knownAs".
-[plantuml]
-----
-hide empty members
-hide methods
+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.
-class Owner {
- +id
- ..
- #lastName
- #firstName
- ..
- -phoneNumber
- -emailAddress
-}
-----
+In this exercise we'll use the same approach with the "telephoneNumber".
+(We won't do "emailAddress" or "knownAs" though - we'll explore an even more powerful way to reduce duplication in the following exercise).
-They are `phoneNumber` and `emailAddress`.
=== Solution
[source,bash]
----
-git checkout tags/03-08-add-remaining-PetOwner-properties
+git checkout tags/03-10-use-meta-annotations-to-reduce-duplication
mvn clean install
mvn -pl spring-boot:run
----
@@ -690,73 +680,73 @@ mvn -pl spring-boot:run
.PhoneNumber.java
----
@Property(
- editing = Editing.ENABLED, // <.>
+ editing = Editing.ENABLED,
+ maxLength = PhoneNumber.MAX_LEN,
+ optionality = Optionality.OPTIONAL
+)
+@Parameter(
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 = 30;
+ int MAX_LEN = 40;
}
----
-<.> any properties annotated with this meta-annotation will be editable by default
-* Similarly, create an `@EmailAddress` meta-annotation, defined to be an editable property:
+* update the `telephoneNumber` of `PetOwner` to use the meta-annotation:
+
[source,java]
-.EmailAddress.java
+.PetOwner.java
----
-@Property(
- editing = Editing.ENABLED,
- maxLength = EmailAddress.MAX_LEN,
- optionality = Optionality.OPTIONAL
-)
-@PropertyLayout(named = "E-mail") // <.>
-@Parameter(maxLength = EmailAddress.MAX_LEN, optionality = Optionality.OPTIONAL)
-@ParameterLayout(named = "E-mail") // <.>
-@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
-@Retention(RetentionPolicy.RUNTIME)
-public @interface EmailAddress {
-
- int MAX_LEN = 100;
-}
+@PhoneNumber // <.>
+@Column(length = PhoneNumber.MAX_LEN, nullable = true, name = "telephoneNumber") // <.>
+@Getter @Setter
+@PropertyLayout(fieldSetId = "contact", sequence = "1.1") // <.>
+private String telephoneNumber;
----
-<.> xref:refguide:applib:index/annotation/PropertyLayout.adoc#named[@PropertyLayout#named] allows characters to be used that are not valid Java identifiers.
-<.> xref:refguide:applib:index/annotation/ParameterLayout.adoc#named[@ParameterLayout#named] - ditto.
+<.> 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.
-* add properties to `PetOwner`:
+* and update the `PetOwners#create()` action method also:
+
[source,java]
-.PetOwner.java
+.PetOwners.java
----
-@PhoneNumber
-@Column(length = PhoneNumber.MAX_LEN, nullable = true)
-@PropertyLayout(fieldSetId = "name", sequence = "1.5")
-@Getter @Setter
-private String phoneNumber;
-
-@EmailAddress
-@Column(length = EmailAddress.MAX_LEN, nullable = true)
-@PropertyLayout(fieldSetId = "name", sequence = "1.6")
-@Getter @Setter
-private String emailAddress;
+@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-9-validation]
-== Ex 3.9: Validation
+[#exercise-3-11-validation]
+== Ex 3.11: Validation
-At the moment there are no constraints for the format of `phoneNumber` or `emailAddress` properties.
+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]
----
-git checkout tags/03-09-validation-rules-using-metaannotations
+git checkout tags/03-11-validation-rules-using-metaannotations
mvn clean install
mvn -pl spring-boot:run
----
@@ -773,49 +763,35 @@ mvn -pl spring-boot:run
editing = Editing.ENABLED,
maxLength = PhoneNumber.MAX_LEN,
optionality = Optionality.OPTIONAL,
- regexPattern = "[+]?[0-9 ]+", // <.>
- regexPatternReplacement = // <.>
- "Specify only numbers and spaces, optionally prefixed with '+'. " +
- "For example, '+353 1 555 1234', or '07123 456789'"
+ 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>
)
-@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 = 30;
+ 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
-* Similarly, update `@EmailAddress`:
-+
-[source,java]
-.EmailAddress.java
-----
-@Property(
- editing = Editing.ENABLED,
- maxLength = EmailAddress.MAX_LEN,
- optionality = Optionality.OPTIONAL,
- regexPattern = "[^@]+@[^@]+[.][^@]+", // <.>
- regexPatternReplacement = "Invalid email address" // <.>
-)
-@PropertyLayout(named = "E-mail")
-@Parameter(maxLength = EmailAddress.MAX_LEN, optionality = Optionality.OPTIONAL)
-@ParameterLayout(named = "E-mail")
-@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
-@Retention(RetentionPolicy.RUNTIME)
-public @interface EmailAddress {
+Try out the application and check that these rules are applied.
- int MAX_LEN = 100;
-}
-----
-<.> regex constraint.
-(Should really use a more comprehensive regex, eg see https://emailregex.com).
-<.> 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:
@@ -834,25 +810,38 @@ public String validate0UpdateName(String newName) { // <.>
<.> validates the "0^th^" parameter of `updateName`.
More details on the validate supporting method can be found xref:refguide:applib-methods:prefixes.adoc#validate[here].
-We can Move this constraint onto the `@LastName` meta-annotation instead:
+In this exercise we'll move this constraint onto the `@Name` meta-annotation instead, using a xref:refguide:applib:index/spec/Specification.adoc[].
+
-* Update the `@LastName` meta-annotation using a xref:refguide:applib-classes:spec.adoc#specification[Specification]:
+=== Solution
+
+[source,bash]
+----
+git checkout tags/03-12-moves-validation-onto-metaannotation
+mvn clean install
+mvn -pl spring-boot:run
+----
+
+=== Task
+
+* Update the `@Name` meta-annotation using a xref:refguide:applib-classes:spec.adoc#specification[Specification]:
+
[source,java]
-.LastName.java
+.Name.java
----
-@Property(maxLength = LastName.MAX_LEN, mustSatisfy = LastName.Spec.class) // <.>
-@Parameter(maxLength = LastName.MAX_LEN, mustSatisfy = LastName.Spec.class) // <1>
-@ParameterLayout(named = "Last Name")
+@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 LastName {
+public @interface Name {
int MAX_LEN = 40;
+ String PROHIBITED_CHARACTERS = "&%$!";
- class Spec extends AbstractSpecification<String> { // <.>
+ class Spec extends AbstractSpecification<String> { // <.>
@Override public String satisfiesSafely(String candidate) {
- for (char prohibitedCharacter : "&%$!".toCharArray()) {
+ for (char prohibitedCharacter : PROHIBITED_CHARACTERS.toCharArray()) {
if( candidate.contains(""+prohibitedCharacter)) {
return "Character '" + prohibitedCharacter + "' is not allowed.";
}
@@ -865,19 +854,221 @@ public @interface LastName {
<.> 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` from `PetOwner`.
+* 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.
-=== Optional exercise
+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.
-NOTE: If you decide to do this optional exercise, make the changes on a git branch so that you can resume with the main flow of exercises later.
-As well as validating the `lastName`, it would be nice to also validate `firstName` with the same rule.
-As the logic is shared, create a new meta-(meta-)annotation called `@Name`, move the specification (and anything else that is common between lastName and firstName) to that new meta annotation, and then meta-annotate `@LastName` and `@FirstName` with `@Name`.
+[#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]
+----
+git checkout tags/03-13-scalar-custom-value-type-for-email-address
+mvn clean install
+mvn -pl spring-boot:run
+----
+
+=== 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 render 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
[#exercise-3-10-field-layout]
@@ -983,70 +1174,3 @@ You might find though that the main benefit of the layout file is to declare how
It really is a matter of personal preference which approach you use.
-
-[#exercise-3-11-column-orders]
-== Ex 3.11: Column Orders
-
-The home page of the webapp shows a list of all `PetOwner`s (inherited from the original simple app).
-We also see a list of `PetOwner`s if we invoke menu:Pet Owners[List All].
-
-The first is a "parented" collection (it is parented by the home page view model), the second is a standalone collection (it is returned from an action).
-
-The properties that are shown as columns that are shown is based on two different mechanisms.
-The first is whether the property is visible at all in any tables, which can be specified using `@PropertyLayout(hidden=...)` (see xref:refguide:applib:index/annotation/PropertyLayout.adoc#hidden[@PropertyLayout#hidden]).
-The second is to use a "columnOrder" file.
-
-In this exercise, we'll use the latter approach.
-
-=== Solution
-
-[source,bash]
-----
-git checkout tags/03-11-PetOwner-columnOrder
-mvn clean install
-mvn -pl spring-boot:run
-----
-
-=== Task
-
-* Declare the `id` field of `PetOwner` as a property by adding a getter and other annotations:
-+
-[source,java]
-.PetOwner.java
-----
-@Id
-@GeneratedValue(strategy = GenerationType.AUTO)
-@Column(name = "id", nullable = false)
-@Getter @Setter // <.>
-@PropertyLayout(fieldSetId = "metadata", sequence = "1") // <.>
-private Long id;
-----
-<.> makes field available as a property
-<.> positions property in the metadata fieldset (before `version`).
-
-* update the columnOrder for standalone collections of `PetOrder`:
-+
-[source,java]
-.PetOwner.columnOrder.txt
-----
-name
-id
-#version
-----
-+
-This will show only `name` and `id`; none of the other properties will be visible as columns.
-
-* create a new file `HomePageViewModel#objects.columnOrder.txt` (in the same package as `HomePageViewModel`) to define the columns visible in the `objects` collection of that view model:
-+
-[source,java]
-.HomePageViewModel#objects.columnOrder.txt
-----
-name
-id
-#version
-----
-
-* delete the (unused) `PetOwner#others.columnOrder.txt` file.
-
-Run the application and confirm the columns are as expected.
-You should also be able to update the files and reload changes (on IntelliJ, menu:Run[Debugging Actions > Reload Changed Classes]) and inspect the updates without having to restart the app.
diff --git a/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc b/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
index 6ebd2b2b7f..760a44e164 100644
--- a/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
@@ -1,15 +1,11 @@
TODO: ideas for future steps:
-- introduce a meta-annotation
-- introduce a custom type for these
+* more user-friendly error message if hit duplicate (enter two pet owners with same name)
-add validation for telephone number and email address.
-
PetOwner#daysSinceLastVisit could be made into a mixin - eg if these marketing analytics were the responsibility of some other module.
-
hidden properties
(causeway) 11/29: CAUSEWAY-2873: 03-14
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit ad7ed85218ee8879edd7248f1b18fdc59016b1ed
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sun May 26 10:51:42 2024 +0100
CAUSEWAY-2873: 03-14
---
.../petclinic/pages/030-petowner-entity.adoc | 205 ++++++++++++++-------
1 file changed, 142 insertions(+), 63 deletions(-)
diff --git a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
index 33c41617cd..496b871771 100644
--- a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
@@ -1071,106 +1071,185 @@ public PetOwner create(
Run the application and try to enter an invalid
-[#exercise-3-10-field-layout]
-== Ex 3.10: Field layout
+[#exercise-3-14-use-layout-xml-file-for-ui-semantics]
+== Ex 3.14: Use layout xml file for UI semantics
-At the moment all the properties of `PetOwner` are grouped into a single fieldset.
-The UI would be improved by grouping properties according to their nature, for example the "phoneNumber" and "emailAddress" in a "Contact Details" fieldset.
+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 move 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.
-We do this using the associated `PetOwner.layout.xml` file (which defines the positioning of the fieldsets), and also using the annotations within `PetOwner` (which associate the properties to those fieldsets).
=== Solution
[source,bash]
----
-git checkout tags/03-10-PetOwner-fieldsets
+git checkout tags/03-14-PetOwner-fieldsets
mvn clean install
mvn -pl spring-boot:run
----
=== Task
-* modify the `PetOwner.layout.xml`, adding two new `fieldSet` definitions after the first `tabGroup`:
+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
----
-<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<bs:grid>
- <bs:row>
- <!-- ... -->
- </bs:row>
- <bs:row>
- <bs:col span="6">
- <bs:tabGroup>
- <!-- ... -->
- </bs:tabGroup>
- <c:fieldSet id="contactDetails" name="Contact Details"/> <!--.-->
- <c:fieldSet id="notes" name="Notes"/> <!--.-->
- </bs:col>
- <bs:col span="6">
- <!-- ... -->
- </bs:col>
- </bs:row>
-</bs:grid>
-----
-<.> fieldSet for contact details
-<.> fieldset for the notes
-
-* modify the `@PropertyLayout` annotation for the properties to associate with these fieldsets:
+<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
----
-// ...
-@PropertyLayout(fieldSetId = "contactDetails", sequence = "1") // <.>
-private String phoneNumber;
-
-// ...
-@PropertyLayout(fieldSetId = "contactDetails", sequence = "2") // <.>
-private String emailAddress;
+@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 using the configuration of the framework is concerned).
-// ...
-@PropertyLayout(fieldSetId = "notes", sequence = "1") // <.>
-private String notes;
+** 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>
----
-<.> associates as the 1^st^ property in the "contact details" fieldset
-<.> associates as the 2^nd^ property in the "contact details" fieldset
-<.> associates with the "notes" 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.
-Run the application; the layout should look like:
-image::03-10/fieldsets.png[width=800]
+* 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
-The layout file can be reloaded dynamically (on IntelliJ, menu:Run[Debugging Actions > Reload Changed Classes]), so you can inspect any updates without having to restart the app.
-Experiment with this by moving a fieldset into a tab group, or change the width of a column).
+** 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
-=== Optional Exercise
+** add to `PetOwner.layout.xml`:
++
+[source,xml]
+.PetOwner.layout.xml
+----
+<cpt:property id="attachment">
+ <cpt:action id="updateAttachment" position="PANEL"/>
+</cpt:property>
+----
-NOTE: If you decide to do this optional exercise, make the changes on a git branch so that you can resume with the main flow of exercises later.
-It is also possible to associate the properties to fieldsets using only the `.layout.xml` file.
-In fact, pretty much all of the metadata in the `@XxxLayout` annotations can be specified in the layout file.
+** 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
----
-<c:fieldSet id="contactDetails" name="Contact Details">
- <c:property id="phoneNumber"/>
- <c:property id="emailAddress"/>
-</c:fieldSet>
-<c:fieldSet id="notes" name="Notes">
- <c:property id="notes"/>
-</c:fieldSet>
+<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
-The `@PropertyLayout` annotations could then be removed.
-
-Using the layout file to specify individual properties provides even more fine-grained control when dynamically reloading, so you could for example switch the order of properties in a fieldset and inspect the changes immediately without having to restart the app.
-You might find though that the main benefit of the layout file is to declare how the different "regions" of the UI fit together in terms of rows, columns, tabs and fieldsets, and then use annotations to slot the properties/actions into those regions.
-It really is a matter of personal preference which approach you use.
+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.
(causeway) 21/29: CAUSEWAY-2873: 08-02
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 90fba5e12b2f14c95153c9ddead5487d7a60247d
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Mon May 27 00:02:16 2024 +0100
CAUSEWAY-2873: 08-02
---
.../modules/petclinic/pages/080-view-models.adoc | 309 ++++++++-------------
1 file changed, 123 insertions(+), 186 deletions(-)
diff --git a/antora/components/tutorials/modules/petclinic/pages/080-view-models.adoc b/antora/components/tutorials/modules/petclinic/pages/080-view-models.adoc
index 9bda1d0087..4df2b2bb50 100644
--- a/antora/components/tutorials/modules/petclinic/pages/080-view-models.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/080-view-models.adoc
@@ -6,7 +6,7 @@ So far the application consists only of domain entities and domain services.
However, the framework also supports view models.
A classic use case is to provide a home page or dashboard, but they are also used to represent certain specific business processes when there isn't necessarily a domain entity required to track the state of the process itself.
-Some real-world examples include importing/exporting spreadsheets periodically (eg changes to indexation rates), or generating extracts such as a payment file or summary PDF for an quarterly invoice run.
+Some real-world examples include importing/exporting spreadsheets periodically (eg changes to indexation rates), or generating extracts such as a payment file or summary PDF for a quarterly invoice run.
@@ -28,71 +28,109 @@ mvn -pl spring-boot:run
=== Tasks
-* update `PetRepository` and `VisitRepository` to extend `JpaRepository` (rather than simply `Repository`)
+* update `VisitRepository` to list all visits after a certain time:
+
-This will provide additional finders "for free".
+[source,java]
+----
+public interface VisitRepository extends Repository<Visit, Integer> {
+
+ List<Visit> findByVisitAtAfter(LocalDateTime visitAt);
+
+ // ...
+}
+----
-* modify `HomePageViewModel` to show the current ``PetOwner``s, ``Pet``s and ``Visit``s in three separate columns:
+* modify `HomePageViewModel` to show the current ``PetOwner``s and any ``Visit``s in the future:
+
[source,java]
----
-@Named("petclinic.HomePageViewModel")
-@DomainObject( nature = Nature.VIEW_MODEL ) // <.>
-@HomePage // <.>
+@Named(SimpleModule.NAMESPACE + ".HomePageViewModel")
+@DomainObject(nature = Nature.VIEW_MODEL) // <.>
+@HomePage // <.>
@DomainObjectLayout()
public class HomePageViewModel {
- public String title() {
- return getPetOwners().size() + " owners";
- }
+ // ...
- public List<PetOwner> getPetOwners() { // <.>
- return petOwnerRepository.findAll();
- }
- public List<Pet> getPets() { // <.>
- return petRepository.findAll();
- }
- public List<Visit> getVisits() { // <.>
- return visitRepository.findAll();
+ @Collection
+ @CollectionLayout(tableDecorator = TableDecorator.DatatablesNet.class)
+ public List<Visit> getFutureVisits() { // <.>
+ LocalDateTime now = clockService.getClock().nowAsLocalDateTime();
+ return visitRepository.findByVisitAtAfter(now);
}
- @Inject PetOwnerRepository petOwnerRepository;
- @Inject PetRepository petRepository;
+ @Inject ClockService clockService;
@Inject VisitRepository visitRepository;
+
}
----
-<.> indicates that this is a view model.
+<.> indicates that this is a xref:userguide::view-models.adoc[view model].
+Causeway provides several ways of implementing view models; this is the most straightforward.
<.> exactly one view model can be annotated as the xref:refguide:applib:index/annotation/HomePage.adoc[@HomePage]
-<.> renamed derived collection, returns ``PetOwner``s.
-<.> new derived collection returning all ``Pet``s.
-<.> new derived collection returning all ``Visits``s.
+<.> new collection returning future ``Visits``s.
-* update the `HomePageViewModel.layout.xml`:
+* update the `HomePageViewModel.layout.xml`.
++
+Here it is in its entirety:
+
[source,xml]
.HomePageViewModel.layout.xml
----
-<!-- ... -->
- <bs:row>
- <bs:col span="12" unreferencedCollections="true">
- <bs:row>
- <bs:col span="4">
- <collection id="petOwners" defaultView="table"/>
- </bs:col>
- <bs:col span="4">
- <collection id="pets" defaultView="table"/>
- </bs:col>
- <bs:col span="4">
- <collection id="visits" defaultView="table"/>
- </bs:col>
- </bs:row>
- </bs:col>
- </bs:row>
-<!-- ... -->
+<?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">
+ <bs3:row>
+ <bs3:col span="12" unreferencedActions="true">
+ <cpt:domainObject/>
+ <cpt:action id="clearHints" hidden="EVERYWHERE"/>
+ <cpt:action id="impersonate" hidden="EVERYWHERE"/>
+ <cpt:action id="impersonateWithRoles" hidden="EVERYWHERE"/>
+ <cpt:action id="stopImpersonating" hidden="EVERYWHERE"/>
+ <cpt:action id="downloadLayoutXml" hidden="EVERYWHERE"/>
+ <cpt:action id="inspectMetamodel" hidden="EVERYWHERE"/>
+ <cpt:action id="rebuildMetamodel" hidden="EVERYWHERE"/>
+ <cpt:action id="downloadMetamodelXml" hidden="EVERYWHERE"/>
+ <cpt:action id="openRestApi" hidden="EVERYWHERE"/>
+ </bs3:col>
+ </bs3:row>
+ </bs3:col>
+ <bs3:col span="6" unreferencedCollections="true">
+ <bs3:row>
+ <bs3:col span="12">
+ <cpt:collection id="petOwners" defaultView="table"/>
+ </bs3:col>
+ </bs3:row>
+ </bs3:col>
+ <bs3:col span="6">
+ <bs3:row>
+ <bs3:col span="12">
+ <cpt:collection id="futureVisits" defaultView="table"/>
+ </bs3:col>
+ </bs3:row>
+ </bs3:col>
+ </bs3:row>
+ <bs3:row>
+ <bs3:col span="0">
+ <cpt:fieldSet name="General" id="general" unreferencedProperties="true"/>
+ </bs3:col>
+ </bs3:row>
+</bs3:grid>
+----
+
+* add `columnOrder.txt` files for the new collection:
++
+[source,text]
+.HomePageViewModel#futureVisits.columnOrder.txt
+----
+pet
+visitAt
+#version
----
-
-* update or add columnOrder.txt files for the 3 collections.
-
[#exercise-8-2-add-a-convenience-action]
@@ -114,47 +152,67 @@ mvn -pl spring-boot:run
=== Tasks
-* create a bookVisit action for HomePageViewModel, as a mixin:
+* add a new finder to `VisitRepository`:
++
+[source,java]
+.VisitRepository.java
+----
+Visit findByPetAndVisitAt(Pet pet, LocalDateTime visitAt);
+----
+
+* create a `bookVisit` action for `HomePageViewModel`, as a mixin:
+
[source,java]
.HomePageViewModel_bookVisit.java
----
@Action // <.>
+@ActionLayout(associateWith = "futureVisits")
@RequiredArgsConstructor
-public class HomePageViewModel_bookVisit { // <.>
+public class HomePageViewModel_bookVisit {
final HomePageViewModel homePageViewModel;
+ @MemberSupport
public Object act(
- PetOwner petOwner, Pet pet, LocalDateTime visitAt, String reason,
- boolean showVisit) { // <.>
- Visit visit = wrapperFactory.wrapMixin(Pet_bookVisit.class, pet).act(visitAt, reason); // <.>
- return showVisit ? visit : homePageViewModel;
+ PetOwner petOwner, Pet pet, LocalDateTime visitAt,
+ boolean showVisit) { // <.>
+ wrapperFactory.wrapMixin(PetOwner_bookVisit.class, petOwner).act(pet, visitAt); // <.>
+ if (showVisit) {
+ return visitRepository.findByPetAndVisitAt(pet, visitAt);
+ }
+ return homePageViewModel;
}
+ @MemberSupport
public List<PetOwner> autoComplete0Act(final String lastName) { // <.>
- return petOwnerRepository.findByLastNameContaining(lastName);
+ return petOwnerRepository.findByNameContaining(lastName);
}
- public List<Pet> choices1Act(PetOwner petOwner) { // <.>
- if(petOwner == null) return Collections.emptyList();
- return petRepository.findByPetOwner(petOwner);
+ @MemberSupport
+ public Set<Pet> choices1Act(PetOwner petOwner) { // <.>
+ if(petOwner == null) {
+ return Collections.emptySet();
+ }
+ return petOwner.getPets();
}
+ @MemberSupport
public LocalDateTime default2Act(PetOwner petOwner, Pet pet) { // <.>
- if(pet == null) return null;
- return factoryService.mixin(Pet_bookVisit.class, pet).default0Act();
+ if(petOwner == null || pet == null) {
+ return null;
+ }
+ return factoryService.mixin(PetOwner_bookVisit.class, petOwner).default1Act();
}
+ @MemberSupport
public String validate2Act(PetOwner petOwner, Pet pet, LocalDateTime visitAt) { // <.>
- return factoryService.mixin(Pet_bookVisit.class, pet).validate0Act(visitAt);
+ return factoryService.mixin(PetOwner_bookVisit.class, petOwner).validate1Act(visitAt);
}
- @Inject PetRepository petRepository;
+ @Inject VisitRepository visitRepository;
@Inject PetOwnerRepository petOwnerRepository;
@Inject WrapperFactory wrapperFactory;
@Inject FactoryService factoryService;
}
----
<.> declares this class as a mixin action.
-<.> The action name is derived from the mixin's class ("bookVisit").
-<.> cosmetic flag to control the UI; either remain at the home page or navigate to the newly created `Visit
+<.> cosmetic flag to control the UI; either remain at the home page or navigate to the newly created `Visit`
<.> use the xref:refguide:applib:index/services/wrapper/WrapperFactory.adoc[WrapperFactory] to delegate to the original behaviour "as if" through the UI.
If additional business rules were added to that delegate, then the mistake would be detected.
<.> Uses an xref:refguide:applib-methods:prefixes.adoc#autoComplete[autoComplete] supporting method to look up matching ``PetOwner``s based upon their name.
@@ -162,135 +220,14 @@ If additional business rules were added to that delegate, then the mistake would
<.> Computes a default for the 2^nd^ parameter, once the first two are selected.
<.> surfaces (some of) the business rules of the delegate mixin.
-* update the layout file to position:
+* update the title of `HomePageViewModel`:
+
[source,xml]
.HomePageViewModel.layout.xml
----
-<!-- ... -->
- <bs:row>
- <bs:col span="12" unreferencedActions="true">
- <domainObject/>
- <action id="bookVisit"/>
- <!-- ... -->
- </bs:col>
- </bs:row>
-<!-- ... -->
-----
-
-
-
-[#exercise-8-3-using-a-view-model-as-a-projection-of-an-entity]
-== Ex 8.3: Using a view model as a projection of an entity
-
-In the home page, the ``Visit`` instances show the `Pet` but they do not show the `PetOwner`.
-One option (probably the correct one in this case) would be to extend `Visit` itself and show this derived information:
-
-[source,java]
-.Visit.java
-----
-public PetOwner getPetOwner() {
- return getPet().getOwner();
-}
-----
-
-Alternatively, if we didn't want to "pollute" the entity with this derived property, we could use a mixin:
-
-[source,java]
-.Visit_petOwner.java
-----
-@Property
-@RequiredArgsConstructor
-public class Visit_petOwner {
-
- final Visit visit;
-
- public PetOwner prop() {
- return visit.getPet().getOwner();
- }
-}
-----
-
-Even so, this would still make the "petOwner" property visible everywhere that a `Visit` is displayed.
-
-If we instead want to be more targetted and _only_ show this "petOwner" property when displayed on the HomePage, yet another option is to implement the xref:refguide:applib:index/services/tablecol/TableColumnVisibilityService.adoc[TableColumnVisibilityService] SPI.
-This provides the context for where an object is being rendered, so this could be used to suppress the collection everywhere except the home page.
-
-A final option though, which we'll use in this exercise, is to display not the entity itself but instead a view model that "wraps" the entity and supplements with the additional data required.
-
-
-=== Solution
-
-[source,bash]
-----
-git checkout tags/08-03-view-model-projecting-an-entity
-mvn clean install
-mvn -pl spring-boot:run
-----
-
-
-=== Tasks
-
-* create a JAXB style view model `VisitPlusPetOwner`, wrapping the `Visit` entity:
-+
-[source,java]
-.VisitPlusPetOwner.java
-----
-@Named("petclinic.VisitPlusPetOwner")
-@DomainObject(nature=Nature.VIEW_MODEL)
-@DomainObjectLayout(named = "Visit")
-@XmlRootElement // <.>
-@XmlType // <1>
-@XmlAccessorType(XmlAccessType.FIELD) // <1>
-@NoArgsConstructor
-public class VisitPlusPetOwner {
-
- @Property(
- projecting = Projecting.PROJECTED, // <.>
- hidden = Where.EVERYWHERE // <.>
- )
- @Getter
- private Visit visit;
-
- VisitPlusPetOwner(Visit visit) {this.visit = visit;}
-
- public Pet getPet() {return visit.getPet();} // <.>
- public String getReason() {return visit.getReason();} // <4>
- public LocalDateTime getVisitAt() {return visit.getVisitAt();} // <4>
-
- public PetOwner getPetOwner() { // <.>
- return getPet().getPetOwner();
- }
+@ObjectSupport public String title() {
+ return getPetOwners().size() + " pet owners, " +
+ getFutureVisits() + " future visits";
}
----
-<.> Boilerplate for JAXB view models
-<.> if the icon/title is clicked, then traverse to this object rather than the view model.
-(The view model is a "projection" of the underlying `Visit`).
-<.> Nevertheless, hide this property from the UI.
-<.> expose properties from the underlying `Visit` entity
-<.> add in additional derived properties, in this case the ``Pet``'s owner.
-
-* Refactor the `getVisits` collection of `HomePageViewModel` to use the new view model:
-+
-[source,java]
-.VisitPlusPetOwner.java
-----
-public List<VisitPlusPetOwner> getVisits() {
- return visitRepository.findAll()
- .stream()
- .map(VisitPlusPetOwner::new)
- .collect(Collectors.toList());
-}
-----
-
-* update the columnOrder file for this collection to display the new property:
-+
-[source,java]
-.HomePageViewModel#visits.columnOrder.txt
-----
-petOwner
-pet
-visitAt
-----
-Run the application; the `visits` collection on the home page should now show the `PetOwner` as an additional column, but otherwise behaves the same as previously.
(causeway) 01/29: CAUSEWAY-2873: updates petclinic tutorial up to 2.5 incl.
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 90a25fe4797a4ea5c612570a30942a7d23806841
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Wed May 22 23:21:27 2024 +0100
CAUSEWAY-2873: updates petclinic tutorial up to 2.5 incl.
---
.../petclinic/pages/010-getting-started.adoc | 40 ++-
.../petclinic/pages/020-the-petclinic-domain.adoc | 317 ++++++++++++++++++++-
.../partials/fixture-scripts/api-and-usage.adoc | 1 +
3 files changed, 330 insertions(+), 28 deletions(-)
diff --git a/antora/components/tutorials/modules/petclinic/pages/010-getting-started.adoc b/antora/components/tutorials/modules/petclinic/pages/010-getting-started.adoc
index ec13043e16..a4a1e6e8a8 100644
--- a/antora/components/tutorials/modules/petclinic/pages/010-getting-started.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/010-getting-started.adoc
@@ -10,11 +10,11 @@ You'll need:
* Java 11
+
-Apache Causeway should work up to at least Java 15, but stick with Java 11 for now.
+Apache Causeway works up to at least Java 21, but stick with Java 11 for now.
-* Maven 3.6.x
+* Maven 3.9.x
+
-Maven 3.6.x or later is recommended.
+Maven 3.9.x or later is recommended.
* git
+
@@ -25,14 +25,14 @@ This has multiple tags for the various checkpoints so you can pick up the tutori
+
The Apache Causeway website has detailed documentation for setting up to use xref:setupguide:intellij:about.adoc[IntelliJ] or xref:setupguide:eclipse:about.adoc[Eclipse].
+
-For this tutorial, make sure that your IDE is configured to support Lombok.
+For this tutorial, also make sure that your IDE is configured to support Lombok.
[#exercise-1-1-starter-apps-clone-the-repo]
== Ex 1.1: Starter apps / clone the repo
-Apache Causeway provides a two starter apps, xref:docs:starters:helloworld.adoc[HelloWorld] and xref:docs:starters:simpleapp.adoc[SimpleApp].
+Apache Causeway provides two starter apps, xref:docs:starters:helloworld.adoc[HelloWorld] and xref:docs:starters:simpleapp.adoc[SimpleApp].
These are identical in terms of functionality, but the simpleapp provides more structure and includes example tests.
NOTE: there are JPA and JDO variants of the starter apps.
@@ -40,7 +40,6 @@ This tutorial uses JPA as it is the more commonly used persistence mechanism.
We start the tutorial simply with a copy of xref:docs:starters:simpleapp.adoc[SimpleApp]; in subsequent exercises we'll refactor and build upon it to morph it into the petclinic domain.
-
* Clone the repo:
+
[source,bash]
@@ -59,6 +58,16 @@ This will make it easier to inspect differences between different tags.
git checkout tags/01-01-starter-app
mvn clean install
----
++
+[NOTE]
+====
+The code at this tag was created by copying the `v2-jpa` tag of xref:docs:starters:simpleapp.adoc[SimpleApp]:
+
+[source,bash]
+----
+curl https://codeload.github.com/apache/causeway-app-simpleapp/zip/v2-jpa | jar xv
+----
+====
* run the app:
+
@@ -93,13 +102,11 @@ Running from the command line isn't ideal, so
* load the project into your IDE as a Maven project, build and run.
-* The app is a Spring boot application, so locate the class with a main, and run.
+* The app is a Spring boot application, so locate the class with a `main()` method, and run.
* alternatively, your IDE might also have specialised support for Spring Boot apps, so run the app that way if you wish.
-* with the IDE load
-
-If you want to go deeper, open up the xref:docs:starters:simpleapp.adoc[page describing the SimpleApp] and start to explore the xref:docs:starters:simpleapp.adoc#structure-of-the-app[structure of the app] files.
+If you want to go deeper, use the xref:docs:starters:simpleapp.adoc[Simple App docs] to xref:docs:starters:simpleapp.adoc#structure-of-the-app[explore the structure] of the application.
@@ -111,10 +118,9 @@ Apache Causeway is an implementation of the _naked objects pattern_, which means
[TIP]
====
An ORM such as JPA (EclipseLink or Hibernate) maps domain objects into an RDBMS or other datastore.
-You can think of Apache Causeway (and naked objects) similarly, but it's an OIM - an object _interface_ mapper.
-It maps to the UI layer rather than the persistence layer.
+Apache Causeway does a similar trick, but in the opposite direction: it maps the domain objects to the UI layer.
-Common to both ORMs and OIMs is an internal metamodel; this is where much of the power comes from.
+Common to both is an internal metamodel, which is where much of the power comes from.
====
We can explore this by looking at the classes provided by the starter app:
@@ -167,7 +173,7 @@ Typically this is static and in the same package as the class; see `SimpleObject
*Mini-Exercise*:
-(No solution is provided for this exercise).
+(no solution is provided for this exercise).
* replace the `@Title` annotation with a `title()` method:
+
@@ -190,7 +196,7 @@ In many cases the details of that representation can be inferred directly from t
For example the label of a field for an object's property (eg `SimpleObject#name`) can be derived directly from the name of the object property itself (`getName()`).
In the absence of other metadata, Apache Causeway will render a domain object with its properties to the left-hand side and its collections (if any) to the right.
-The order of these properties and collections can be specified using the `@PropertyLayout` annotation and the `@CollectionLayout` annotation.
+The order of these properties and collections can be specified using the xref:refguide:applib:index/annotation/PropertyLayout.adoc[@PropertyLayout] annotation and the xref:refguide:applib:index/annotation/CollectionLayout.adoc[@CollectionLayout] annotation.
There are other annotations to group properties together and to associate action buttons with either properties or collections.
The downside of using annotations is that changing the layout requires that the application be restarted, and certain more complex UIs, such as multi-columns or tab groups are difficult or impossible to express.
@@ -200,6 +206,8 @@ This is modelled upon bootstrap and so supports arbitrary rows and columns as we
*Mini-Exercise*:
+(no solution is provided for this exercise).
+
* locate the `SimpleObject.layout.xml` file
* compare the structure of the layout file to that of the rendered object
* change the file, eg the relative widths of the columns
@@ -220,6 +228,8 @@ In a similar fashion, the actions of the various domain services are grouped int
*Mini-Exercise*:
+(no solution is provided for this exercise).
+
* locate the `menubars.layout.xml` file
* compare the structure of the layout file to that of the rendered menu bar
* change the file, eg reorder menu items or create new menus
diff --git a/antora/components/tutorials/modules/petclinic/pages/020-the-petclinic-domain.adoc b/antora/components/tutorials/modules/petclinic/pages/020-the-petclinic-domain.adoc
index bd3033c4a4..72d06a82ee 100644
--- a/antora/components/tutorials/modules/petclinic/pages/020-the-petclinic-domain.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/020-the-petclinic-domain.adoc
@@ -29,36 +29,327 @@ Some of the use cases we might want to support include:
This tutorial has worked solutions for all of these.
[#exercise-2-1-refactor-simpleobject-to-petowner]
-== Ex 2.1: Refactor `SimpleObject` to `PetOwner`
+== Ex 2.1: Create a new `pets` module with `PetOwner`
+
+Using the `simple` module as a guide, we'll create a new `petowners` module and create a `PetOwner` and `PetOwners` domain service as reference.
+(We leave the `simple` module untouched, to use as a reference).
-To start with, let's rename the `SimpleObject` entity to `PetOwner`
=== Solution
[source,bash]
----
-git checkout tags/02-01-renames-SimpleObject-to-PetOwner
+git checkout tags/02-01-copies-SimpleObject-to-PetOwner
mvn clean install
mvn -pl spring-boot:run
----
=== Tasks
-Just checkout the solution above and review the git history to see the changes that have already been made.
-These include:
-
-* Domain classes renamed (along with corresponding tests)
+To save time, just checkout the solution tag above and review the git history to see the classes that were copied and renamed:
-** `SimpleObject` entity -> `PetOwner`
-** `SimpleObjects` domain service -> `PetOwners`
+* `dom.petowner` package:
+** `SimpleObject` -> `PetOwner` (entity)
++
+plus related `.layout.xml`, `.png` and `.columnOrder.txt` files
+** `SimpleObjects` -> `PetOwners` (domain service)
** `SimpleObjectRepository` repository service -> `PetOwnerRepository`
+** `SimpleModule` -> `PetOwnerModule` (module)
+* `fixture` package:
+** `PetOwner_persona` (fixture script)
+* `types` package
+** `Name` (meta annotation)
+** `Notes` (meta annotation)
+* root package of module
+** `PetOwnerModule` (Spring `@Configuration`, aka module class)
+
+* In the `PetOwnerModule`, the namespace and schema constants were changed:
++
+[source, java]
+.PetOwnerModule.java
+----
+// ...
+public class PetOwnerModule implements ModuleWithFixtures {
+
+ public static final String NAMESPACE = "petowner";
+ public static final String SCHEMA = "petowner";
+
+ // ...
+}
+----
+
+* in `application.properties`, the "petowner" schema was also added to the xref:refguide:config:sections/causeway.persistence.schema.adoc#causeway.persistence.schema.auto-create-schemas[causeway.persistence.schema.auto-create-schemas] config property:
++
+[source,properties]
+.application.properties
+----
+causeway.persistence.schema.auto-create-schemas=petowner,simple,...
+----
+
+Resource classes were also copied.
+
+In addition, the new Maven module was defined in the top-level `pom.xml`, and included in `<modules>`.
+
+Confirm that the code still builds:
+
+[source,bash]
+----
+mvn install
+----
+
+However, if you run the app confirm that it is still showing just the original `simpleapp` module:
+
+[source,bash]
+----
+mvn -pl webapp spring-boot:run
+----
+
+We'll fix this in the next exercise.
+
+
+[#exercise-2-2-configure-the-app-to-include-the-petowner-module]
+== Ex 2.2: Configure the app to include the `petowner` module
+
+We have our new `petowner` module, but we're not actually using it; if we run the application we still see the original `simpleapp` module.
+
+[source,bash]
+----
+mvn -pl webapp spring-boot:run
+----
-** Infrastructure classes renamed
+In this exercise we'll update the app to include `petowners` as well.
-** `SimpleModule` -> `PetsModule`
-** `SimpleApp` -> `PetClinicApp`
+=== Solution
+
+[source,bash]
+----
+git checkout tags/02-02-configure-the-app-to-include-petowner
+mvn clean install
+mvn -pl spring-boot:run
+----
+
+=== Tasks
+
+Rename the main class that acts as the entry point for the app (annotated with `@@SpringBootApplication`):
+
+* locate the `SimpleApp` class and rename to `PetClinicApp`
+
+* Also update classes that reference this type (in particular, in `pom.xml`).
++
+Your IDE can probably do this for you automatically.
+
+The `PetClinicApp` main class references the `AppManifest`, a top-level Spring `@Configuration` which imports all the other classes.
+
+* Inspect the `AppManifest` class: most will be ``CausewayXxxModule``s
+* note that it also imports the `ApplicationModule`, which defines your own modules which make up the application
+
+We want to update `ApplicationModule` to reference `PetOwnerModule`.
+We need to change both Maven and then Spring:
-Build and run the application (note that the main class has changed) to make sure it still runs fine.
+* in the `pom.xml` (of the `webapp` module), add in a dependency to the `petowner` module:
++
+[source,xml]
+.webapp/pom.xml
+----
+<dependencies>
+
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>simpleapp-jpa-module-petowner</artifactId>
+ </dependency>
+ ...
+<dependencies>
+----
++
+You might need to reimport or refresh so that your IDE rebuilds its classpath.
+
+* in the `ApplicationModule`, update the `@Import` to reference the `PetOwnerModule`:
++
+[source,java]
+.Application.java
+----
+@Configuration
+@Import({
+ PetOwnerModule.class, //<.>
+ SimpleModule.class,
+})
+@ComponentScan
+public class ApplicationModule {
+
+}
+----
+<.> add this import
+
+Run up the application using the IDE or from maven command line.
+Although everything looks good, in fact you won't see any change.
+That's because the simpleapp starter app includes the xref:security:secman:about.adoc[SecMan] security module, for users, roles and permissions.
+We'll fix this in the next exercise.
+
+
+[#exercise-2-3-add-security-role-for-petowner-module]
+== Ex 2.3: Add security role for the `petowner` module
+
+In this exercise we'll add a security role to grant access to our new `petowner` module, and we'll make sure that our "sven" demo user is a member of this role.
+
+=== Solution
+
+[source,bash]
+----
+git checkout tags/02-03-add-security-role-for-petowner
+mvn clean install
+mvn -pl spring-boot:run
+----
+
+=== Tasks
+
+The starter app is configured to use an in-memory database.
+The xref:security:secman:about.adoc[SecMan] roles and permissions are therefore set up each time the applicatoin is bootstrapped, using a fixture scripts.
+We'll learn more about fixture scripts and their role in testing in a later exercise.
+For now, all that's necessary to know is that we can use them to set up the roles.
+
+* Locate the `CustomRolesAndUsers` fixture script class
+
+* within it, copy the existing `SimpleModuleSuperuserRole` to create a similar `PetOwnerModuleSuperuserRole`:
++
+[source,java]
+.CustomRolesAndUsers.java
+----
+private static class PetOwnerModuleSuperuserRole
+ extends AbstractRoleAndPermissionsFixtureScript {
+
+ public static final String ROLE_NAME = "petowner-superuser";
+
+ public PetOwnerModuleSuperuserRole() {
+ super(ROLE_NAME, "Permission to use everything in the 'petowner' module");
+ }
+
+ @Override
+ protected void execute(ExecutionContext executionContext) {
+ newPermissions(
+ ApplicationPermissionRule.ALLOW,
+ ApplicationPermissionMode.CHANGING,
+ Can.of(ApplicationFeatureId.newNamespace("petowner"))
+ );
+ }
+}
+----
+
+* further down in the same class, update the fixture script for the "sven" end-user, and add them into our new role:
++
+[source,java]
+.CustomRolesAndUsers.java
+----
+private static class SvenUser extends AbstractUserAndRolesFixtureScript {
+ ...
+
+ private static class RoleSupplier implements Supplier<Can<String>> {
+ @Override
+ public Can<String> get() {
+ return Can.of(
+ causewayConfiguration.getExtensions().getSecman().getSeed().getRegularUser().getRoleName(), // built-in stuff
+ PetOwnerModuleSuperuserRole.ROLE_NAME, // <.>
+ SimpleModuleSuperuserRole.ROLE_NAME
+ );
+ }
+ @Inject CausewayConfiguration causewayConfiguration;
+ }
+}
+----
+<.> reference new role.
+
+* and in the `execute()` method at top of the class, remember to include the call to the new fixture script:
++
+[source,java]
+.CustomRolesAndUsers.java
+----
+@Override
+protected void execute(ExecutionContext executionContext) {
+ executionContext.executeChildren(this,
+ new SimpleModuleSuperuserRole(),
+ new PetOwnerModuleSuperuserRole(), // <.>
+ new SvenUser());
+}
+----
+<.> Remember to call the new module
+
+
+Now run the application from the IDE or Maven:
+
+* The menubar should now show a new "Other" menu - these are the now-visible actions arising from the `PetOwners` domain service.
+
+* Create a new `PetOwner` using "Other > Create".
++
+The resultant object will look identical in structure to a `SimpleObject`, but you can confirm that it is indeed an instance of a `PetOwner` by hovering over the title or icon and checking the tooltip.
+
+The fact that the `PetOwners` domain service's actions are displayed automatically is an important principle of the _naked objects pattern_ as implemented by Apache Causeway.
+
+[#exercise-2-4-update-menubar-for-PetOwners]
+== Ex 2.4: Update menubar for PetOwners
+
+Let's now update the menu bar so that the actions to create ``PetOwner``s live under an appropriately named menu, "Pet Owners".
+
+=== Solution
+
+[source,bash]
+----
+git checkout tags/02-04-update-menubar-for-petowners
+mvn clean install
+mvn -pl spring-boot:run
+----
+
+=== Tasks
+
+* Locate the `menubars.layout.xml` file.
++
+You should find it in `src/main/resources` (in the `webapp` module).
+
+* Add in the following XML, under the `<mb3:primary>` tag:
++
+[source,xml]
+.menubars.layout.xml
+----
+<mb3:primary>
+ <mb3:menu>
+ <mb3:named>Pet Owners</mb3:named>
+ <mb3:section>
+ <mb3:serviceAction objectType="petowner.PetOwners" id="create"/>
+ <mb3:serviceAction objectType="petowner.PetOwners" id="findByName"/>
+ <mb3:serviceAction objectType="petowner.PetOwners" id="findByNameLike"/>
+ <mb3:serviceAction objectType="petowner.PetOwners" id="listAll"/>
+ </mb3:section>
+ </mb3:menu>
+...
+</mb3:primary>
+----
++
+The `objectType` attribute corresponds to the `@Named` value of the `PetOwners` domain service (its "logical type name"), while the `id` attribute matches the method name of each action.
+
+Run the application; the menubar should be updated correctly.
+
+
+[#exercise-2-5-setup-demo-PetOwners]
+== Ex 2.5: Set up demo PetOwners
+
+xref:testing:fixtures:about.adoc#fixture-scripts[Fixture scripts] are used for both prototyping and for integration testing, allowing us to quickly create demo data in the H2 in-memory database.
+
+The xref:testing:fixtures:about.adoc#personas-and-builders[persona] pattern lets us define example data in an enum (the "what"), while a companion builder uses that data to set up the corresponding domain entities (the "how").
+
+In this exercise we'll modify the `PetOwners_persona` (copied from `SimpleObjects_persona` earlier) to create more realistic data.
+
+
+=== Solution
+
+[source,bash]
+----
+git checkout tags/02-05-setup-demo-petowners
+mvn clean install
+mvn -pl spring-boot:run
+----
+
+=== Tasks
+* locate the `PetOwners_persona` class
+* refactor the enum constants and values to create 10 realistic pet owner names
+* (optional) we'll use the associated `.pdf` to store a "veterinary clinic service agreement" for each pet owner.
diff --git a/testing/fixtures/adoc/modules/fixtures/partials/fixture-scripts/api-and-usage.adoc b/testing/fixtures/adoc/modules/fixtures/partials/fixture-scripts/api-and-usage.adoc
index 43b1160552..86b26e697f 100644
--- a/testing/fixtures/adoc/modules/fixtures/partials/fixture-scripts/api-and-usage.adoc
+++ b/testing/fixtures/adoc/modules/fixtures/partials/fixture-scripts/api-and-usage.adoc
@@ -192,6 +192,7 @@ This was demonstrated in the <<fixturescript, previous section>>
The latter use case is much less frequently used, but can be helpful for example in demos, where the number of objects can be specified in the `parameters` parameter of the xref:refguide:testing:index/fixtures/applib/fixturescripts/FixtureScripts.adoc#runFixtureScript_String_String[run fixture script] action.
+[#personas-and-builders]
== Personas and Builders
Good integration tests are probably the best way to understand the behaviour of the domain model: better, even, than reading the code itself.
(causeway) 18/29: CAUSEWAY-2873: 06-01
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 8c6f8f5161934f6604adf098c6bfc6f8a2cd0cd5
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sun May 26 22:16:39 2024 +0100
CAUSEWAY-2873: 06-01
---
.../petclinic/pages/030-petowner-entity.adoc | 2 +-
.../modules/petclinic/pages/050-visit-entity.adoc | 247 +++++++++++----------
.../modules/petclinic/pages/060-unit-testing.adoc | 40 ++--
.../modules/petclinic/pages/070-modularity.adoc | 2 +-
.../modules/petclinic/partials/domain.adoc | 7 +-
5 files changed, 150 insertions(+), 148 deletions(-)
diff --git a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
index 4b899f7b39..73cc8c874d 100644
--- a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
@@ -807,7 +807,7 @@ public String validate0UpdateName(String newName) { // <.>
return null;
}
----
-<.> validates the "0^th^" parameter of `updateName`.
+<.> 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[].
diff --git a/antora/components/tutorials/modules/petclinic/pages/050-visit-entity.adoc b/antora/components/tutorials/modules/petclinic/pages/050-visit-entity.adoc
index ff93eec71c..380039fa4a 100644
--- a/antora/components/tutorials/modules/petclinic/pages/050-visit-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/050-visit-entity.adoc
@@ -74,6 +74,37 @@ causeway.persistence.schema.auto-create-schemas=\
petowner,visit,simple,...
----
+* add permissions to the new "visit" namespace.
+We could do this by adding a new security role, but for simplicity we'll just add to the existing role (`PetOwnerModuleSuperuserRole), renaming it as we do:
++
+[source,java]
+.CustomRolesAndUsers.java
+----
+private static class PetClinicSuperuserRole // <.>
+ extends AbstractRoleAndPermissionsFixtureScript {
+
+ public static final String ROLE_NAME = "petclinic-superuser"; // <.>
+
+ public PetClinicSuperuserRole() {
+ super(ROLE_NAME, "Permission to use everything in the 'petowner' and 'visit' modules");
+ }
+
+ @Override
+ protected void execute(ExecutionContext executionContext) {
+ newPermissions(
+ ApplicationPermissionRule.ALLOW,
+ ApplicationPermissionMode.CHANGING,
+ Can.of(ApplicationFeatureId.newNamespace("petowner"),
+ ApplicationFeatureId.newNamespace("visit") // <.>
+ )
+ );
+ }
+}
+----
+<.> renamed
+<.> renamed
+<.> added
+
[#exercise-5-2-visit-module-dependencies]
== Ex 5.2: Visit Module Dependencies
@@ -193,7 +224,7 @@ mvn -pl spring-boot:run
@Named(VisitModule.NAMESPACE + ".Visit")
@DomainObject(entityChangePublishing = Publishing.ENABLED)
@DomainObjectLayout()
-@NoArgsConstructor(access = AccessLevel.PUBLIC)
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
@XmlJavaTypeAdapter(PersistentEntityAdapter.class)
@ToString(onlyExplicitlyIncluded = true)
public class Visit implements Comparable<Visit> {
@@ -210,7 +241,7 @@ public class Visit implements Comparable<Visit> {
@Getter @Setter
private long version;
- Visit(Pet pet, LocalDateTime visitAt) {
+ public Visit(Pet pet, LocalDateTime visitAt) {
this.pet = pet;
this.visitAt = visitAt;
}
@@ -330,8 +361,8 @@ image::05-02/Visit-entity.png[]
-[#exercise-5-3-book-visit-action]
-== Ex 5.3: "Book Visit" action
+[#exercise-5-4-book-visit-action]
+== Ex 5.4: "Book Visit" action
We now want to extend our domain model so that ``Visit``s to be created.
@@ -347,34 +378,17 @@ include::partial$domain.adoc[]
Causeway's solution to this is to allow the visit module to define behaviour, but have the behaviour seem to belong to the `Pet` entity, at least so far as the user interface is concerned.
This is done using a xref:userguide::mixins.adoc[].
+Because `Visit` is its own root entity, we're also going to need a repository to be able to look them up for a given `Pet`.
-=== Solution
-
-[source,bash]
-----
-git checkout tags/05-03-book-visit-action
-mvn clean install
-mvn -pl spring-boot:run
-----
-
-
-
-=== Tasks
-
-
-
-[#exercise-5-4-capture-visit-reason]
-== Ex 5.3: Capture visit reason
-
-In addition to the key properties, the `Visit` has one further mandatory property, `reason`.
-This is required to be specified when a `Visit` is created ("what is the purpose of this visit?")
+In this exercise we'll define the repository, and create the "book visit" mixin action (also sometimes called a contributed action.
+We'll also create a mixin _collection_ to be able to view the visits from a ``PetOwner``'s UI, too.
-In this exercise we'll extend the "book visit" action to also capture that reason.
+=== Solution
[source,bash]
----
-git checkout tags/05-03-schedule-visit-action
+git checkout tags/05-04-book-visit-action
mvn clean install
mvn -pl spring-boot:run
----
@@ -382,128 +396,133 @@ mvn -pl spring-boot:run
=== Tasks
-* add the `@Reason` meta-annotation
+* create the `VisitRepository`, using Spring Data:
+
[source,java]
-.Reason.java
+.VisitRepository.java
----
-@Property(maxLength = Reason.MAX_LEN)
-@PropertyLayout(named = "Reason")
-@Parameter(maxLength = Reason.MAX_LEN)
-@ParameterLayout(named = "Reason")
-@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
-@Retention(RetentionPolicy.RUNTIME)
-public @interface Reason {
+import org.springframework.data.repository.Repository;
- int MAX_LEN = 255;
-}
-----
+// ...
-* add the `reason` mandatory property:
-+
-[source,java]
-.Visit.java
-----
-@Reason
-@Column(name = "reason", length = FirstName.MAX_LEN, nullable = false)
-@Getter @Setter
-@PropertyLayout(fieldSetId = "details", sequence = "1")
-private String reason;
-----
+public interface VisitRepository extends Repository<Visit, Long> {
+ @Query("select v from Visit v where v.pet.petOwner = :petOwner")
+ List<Visit> findByPetOwner(PetOwner petOwner);
-* update constructor (as this is a mandatory property)
-+
-[source,java]
-.Visit.java
-----
-Visit(Pet pet, LocalDateTime visitAt, String reason) {
- this.pet = pet;
- this.visitAt = visitAt;
- this.reason = reason;
}
----
-* create a "visits" mixin collection as a mixin of `Pet`, so we can see the ``Visit``s that have been booked:
+* define the "book visit" mixin action on `PetOwner`, in the _visit_ module.
++
+NOTE: we simply use a datetime to capture when the visit occurs.
+This isn't particularly realistic, we know - there would probably be a domain concept such as `AppointmentSlot`.
+
[source,java]
-.Pet_visits.java
+.PetOwner_bookVisit.java
----
-@Collection
-@CollectionLayout(defaultView = "table")
+@Action // <.>
+@ActionLayout(associateWith = "visits") // <.>
@RequiredArgsConstructor
-public class Pet_visits {
+public class PetOwner_bookVisit { // <.>
- private final Pet pet;
+ private final PetOwner petOwner; // <.>
- public List<Visit> coll() {
- return visitRepository.findByPetOrderByVisitAtDesc(pet);
+ @MemberSupport
+ public PetOwner act(Pet pet, LocalDateTime visitAt) {
+ Visit visit = new Visit(pet, visitAt);
+ repositoryService.persistAndFlush(visit); // <.>
+ return petOwner;
+ }
+ @MemberSupport
+ public Set<Pet> choices0Act() { // <.>
+ return petOwner.getPets();
+ }
+ @MemberSupport
+ public Pet default0Act() { // <.>
+ Set<Pet> pets = petOwner.getPets();
+ return pets.size() == 1 ? pets.iterator().next() : null;
+ }
+ @MemberSupport
+ public LocalDateTime default1Act() { // <7>
+ return officeHoursTomorrow();
+ }
+ @MemberSupport
+ public String validate1Act(LocalDateTime visitAt) {
+ if (visitAt.isBefore(officeHoursTomorrow())) {
+ return "Must book in the future";
+ }
+ return null;
}
- @Inject VisitRepository visitRepository;
+ private LocalDateTime officeHoursTomorrow() {
+ return clockService.getClock().nowAsLocalDate().atStartOfDay().plusDays(1).plusHours(9);
+ }
+
+
+ @Inject ClockService clockService;
+ @Inject RepositoryService repositoryService; // <5>
}
----
+<.> indicates that this class is a mixin action
+<.> anticipates there being a "visits" collection also
+<.> the name of the contributed action is inferred from the mixin's class name
+<.> the type to which this mixin is being contributed, that is, `PetOwner`
+<.> injected xref:refguide:applib:index/services/repository/RepositoryService.adoc[] acts as a facade to the database for all entities.
+For querying it's usually worth defining a custom repository.
+<.> the xref:refguide:applib-methods:prefixes.adoc#choices[choices...()] supporting method provides programmatic set of choices for a parameter, in this case for the 0^th^ parameter `Pet`, rendered as a drop-down list.
+<.> the xref:refguide:applib-methods:prefixes.adoc#default[default...()] supporting method returns a default value for a parameter.
-* create a "bookVisit" mixin action (in the visits module), as a mixin of `Pet`.
-+
-We can use xref:refguide:applib:index/services/clock/ClockService.adoc[ClockService] to ensure that the date/time specified is in the future, and to set a default date/time for "tomorrow"
+* define the "visits" mixin collection on `PetOwner`, in the _visit_ module.
+
[source,java]
-.Pet_bookVisit.java
+.PetOwner_visits.java
----
-@Action(
- semantics = SemanticsOf.IDEMPOTENT,
- commandPublishing = Publishing.ENABLED,
- executionPublishing = Publishing.ENABLED
-)
-@ActionLayout(associateWith = "visits", sequence = "1")
+@Collection // <.>
@RequiredArgsConstructor
-public class Pet_bookVisit {
+public class PetOwner_visits {
- private final Pet pet;
+ private final PetOwner petOwner; // <.>
- public Visit act(
- LocalDateTime visitAt,
- @Reason final String reason
- ) {
- return repositoryService.persist(new Visit(pet, visitAt, reason));
- }
- public String validate0Act(LocalDateTime visitAt) {
- return clockService.getClock().nowAsLocalDateTime().isBefore(visitAt) // <.>
- ? null
- : "Must be in the future";
- }
- public LocalDateTime default0Act() {
- return clockService.getClock().nowAsLocalDateTime() // <.>
- .toLocalDate()
- .plusDays(1)
- .atTime(LocalTime.of(9, 0));
+ @MemberSupport
+ public List<Visit> coll() {
+ return visitRepository.findByPetOwner(petOwner);
}
- @Inject ClockService clockService;
- @Inject RepositoryService repositoryService;
+ @Inject VisitRepository visitRepository;
}
----
-<.> ensures that the date/time specified is in the future.
-<.> defaults to 9am tomorrow morning.
-
-Also add in the UI files:
+<.> indicates that this class is a mixin collection
+<.> the type to which this mixin is being contributed, that is, `PetOwner`
-* add a `Pet#visits.columnOrder.txt` file
+* update ``PetOwner``'s `.layout.xml`, to indicate where the contributed `visits` collection should be placed:
+
-to define which properties of Visit are visible as columns in ``Pet``'s `visits` collection.
-
-
-
-=== Optional exercises
-
-NOTE: If you decide to do this optional exercise, make the changes on a git branch so that you can resume with the main flow of exercises later.
+[source,xml]
+.PetOwner.layout.xml
+----
+<bs3:col span="12">
+ <bs3:row>
+ <bs3:col span="12">
+ <cpt:collection id="pets"/>
+ </bs3:col>
+ </bs3:row>
+ <bs3:row>
+ <bs3:col span="12">
+ <cpt:collection id="visits"/>
+ </bs3:col>
+ </bs3:row>
+ <cpt:fieldSet name="Content" id="content">
+ ...
+ </cpt:fieldSet>
+</bs3:col>
+----
++
+[NOTE]
+====
+Strictly speaking, updating the `.layout.xml` _does_ make the `petowner` module aware of `visit` module, albeit it in a very soft way.
-. Download a separate `Visit-NN.png` for each of the days of the month (1 to 31), and then use `iconName()` to show a more useful icon based on the `visitAt` date.
+Alternatively, the `.layout.xml` can be left untouched in which case the contributed `visits` collection will be rendered in the same place as any other "unreferencedCollections".
+====
-. Use choices to provide a set of available date/times, in 15 minutes slots, say.
-. Refine the list of slots to filter out any visits that already exist
-+
-Assume that visits take 15 minutes, and that only on visit can happen at a time.
diff --git a/antora/components/tutorials/modules/petclinic/pages/060-unit-testing.adoc b/antora/components/tutorials/modules/petclinic/pages/060-unit-testing.adoc
index e66530224a..c1a6041706 100644
--- a/antora/components/tutorials/modules/petclinic/pages/060-unit-testing.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/060-unit-testing.adoc
@@ -10,7 +10,7 @@ In this part of the tutorial we'll cover unit testing, later on we'll look at in
[#exercise-6-1-unit-test-the-default-time-when-booking-visits]
== Ex 6.1: Unit test the default time when booking visits
-The xref:050-visit-entity.adoc#exercise-5-3-book-visit-action["Book Visit"] action has a default time of 9am the next morning.
+The xref:050-visit-entity.adoc#exercise-5-4-book-visit-action["Book Visit"] action has a default time of 9am the next morning.
In this section we'll write a unit test to verify this logic, using Mockito to "mock the clock".
@@ -31,23 +31,11 @@ mvn -pl spring-boot:run
[source,xml]
.module-visits/pom.xml
----
-<dependencies>
- <!-- ... -->
-
- <dependency>
- <groupId>org.apache.causeway.mavendeps</groupId>
- <artifactId>causeway-mavendeps-unittests</artifactId>
- <type>pom</type>
- <scope>test</scope>
- <exclusions>
- <exclusion>
- <groupId>org.jmock</groupId>
- <artifactId>jmock-junit4</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
-
-</dependencies>
+<dependency>
+ <groupId>org.apache.causeway.testing</groupId>
+ <artifactId>causeway-testing-unittestsupport-applib</artifactId>
+ <scope>test</scope>
+</dependency>
----
* add the test:
@@ -55,7 +43,7 @@ mvn -pl spring-boot:run
[source,java]
----
@ExtendWith(MockitoExtension.class) // <.>
-class Pet_bookVisit_Test {
+public class PetOwner_bookVisit_Test {
@Mock ClockService mockClockService; // <.>
@Mock VirtualClock mockVirtualClock; // <2>
@@ -66,31 +54,31 @@ class Pet_bookVisit_Test {
}
@Nested
- class default0 {
+ class default1 {
@Test
void defaults_to_9am_tomorrow_morning() {
// given
- Pet_bookVisit mixin = new Pet_bookVisit(null);
+ PetOwner_bookVisit mixin = new PetOwner_bookVisit(null);
mixin.clockService = mockClockService; // <.>
- LocalDateTime now = LocalDateTime.of(2021, 10, 21, 16, 37, 45);
+ LocalDateTime now = LocalDateTime.of(2024, 5, 26, 16, 37, 45);
// expecting
- Mockito.when(mockVirtualClock.nowAsLocalDateTime()).thenReturn(now);// <.>
+ Mockito.when(mockVirtualClock.nowAsLocalDate()) // <.>
+ .thenReturn(now.toLocalDate());
// when
- LocalDateTime localDateTime = mixin.default0Act();
+ LocalDateTime localDateTime = mixin.default1Act();
// then
Assertions.assertThat(localDateTime) // <.>
- .isEqualTo(LocalDateTime.of(2021,10,22,9,0,0));
+ .isEqualTo(LocalDateTime.of(2024,5,27,9,0,0));
}
}
}
----
-
<.> Instructs JUnit to use Mockito for mocking.
<.> mocks the `ClockService`, and mocks the `VirtualClock` returned by the `ClockService`.
Automatically provisioned by Mockito.
diff --git a/antora/components/tutorials/modules/petclinic/pages/070-modularity.adoc b/antora/components/tutorials/modules/petclinic/pages/070-modularity.adoc
index a62595fa5b..793a6aad36 100644
--- a/antora/components/tutorials/modules/petclinic/pages/070-modularity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/070-modularity.adoc
@@ -10,7 +10,7 @@ The framework provides two main tools:
* the first we've already seen is mixins.
+
-These allow us to locate busines logic in one module that "appears" to reside in another module.
+These allow us to locate business logic in one module that "appears" to reside in another module.
Examples are the `visits` mixin collection and `bookVisit` mixin action that are both contributed by the `visits` module to the `Pet` entity in the `pets` module.
* the second is domain events.
diff --git a/antora/components/tutorials/modules/petclinic/partials/domain.adoc b/antora/components/tutorials/modules/petclinic/partials/domain.adoc
index e699f8cd23..44bb4da217 100644
--- a/antora/components/tutorials/modules/petclinic/partials/domain.adoc
+++ b/antora/components/tutorials/modules/petclinic/partials/domain.adoc
@@ -35,6 +35,7 @@ package pets {
-emailAddress
..
-lastVisit
+ -/daysSinceLastVisit
..
-notes
}
@@ -48,12 +49,6 @@ package visits {
..
#pet
#visitAt: LocalDateTime
- ..
- -reason
- ..
- -cost
- -paid: boolean
- -outcome
}
}
(causeway) 17/29: CAUSEWAY-2873: 05-02
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 79ba585cc3843ffe58ca8187111e52e676844dea
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sun May 26 17:43:37 2024 +0100
CAUSEWAY-2873: 05-02
---
.../petclinic/images/05-02/Visit-entity.png | Bin 0 -> 14304 bytes
.../modules/petclinic/pages/050-visit-entity.adoc | 233 +++++++++++++++++----
.../modules/petclinic/pages/100-todo.adoc | 3 +
3 files changed, 201 insertions(+), 35 deletions(-)
diff --git a/antora/components/tutorials/modules/petclinic/images/05-02/Visit-entity.png b/antora/components/tutorials/modules/petclinic/images/05-02/Visit-entity.png
new file mode 100644
index 0000000000..ea0c3a9ab9
Binary files /dev/null and b/antora/components/tutorials/modules/petclinic/images/05-02/Visit-entity.png differ
diff --git a/antora/components/tutorials/modules/petclinic/pages/050-visit-entity.adoc b/antora/components/tutorials/modules/petclinic/pages/050-visit-entity.adoc
index 05b25751be..ff93eec71c 100644
--- a/antora/components/tutorials/modules/petclinic/pages/050-visit-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/050-visit-entity.adoc
@@ -75,64 +75,122 @@ causeway.persistence.schema.auto-create-schemas=\
----
-== next exercise:
+[#exercise-5-2-visit-module-dependencies]
+== Ex 5.2: Visit Module Dependencies
-set up dependencies
-- maven
-- spring
-reference new module in AppManifest
-- remove transitive dependencies
+Although we have a visit module, it currently is unaware of the pet owner module.
+And similarly, the top-level application doesn't yet know about visit module.
+In this exercise we'll fix this, so that:
+
+application -> visit module -> petowner module
+
+
+
+=== Solution
+
+[source,bash]
+----
+git checkout tags/05-02-visit-module-dependencies
+mvn clean install
+mvn -pl spring-boot:run
+----
+
+=== tasks
+
+* in the new visit module's `pom.xml`, add a Maven dependency on `petowner` module:
++
+[source,xml]
+.visit/pom.xml
+----
+<dependency>
+ <groupId>org.apache.causeway.starters</groupId>
+ <artifactId>simpleapp-jpa-module-petowner</artifactId>
+</dependency>
+----
++
+TIP: In your IDE you may need to reload/refresh dependencies to rebuild the classpath.
+
+* in the `VisitModule` Causeway module, add a corresponding import on `PetOwnerModule`:
+
[source,java]
-.VisitsModule.java
+.VisitModule.java
----
+@Configuration
+@Import({
+ PetOwnerModule.class,
+ // ...
+})
+// ...
+public class VisitModule ... { ... }
----
+* in the webapp module, add a Maven dependency on the visit module:
+[source,xml]
+.visit/pom.xml
+----
+<dependency>
+ <groupId>org.apache.causeway.starters</groupId>
+ <artifactId>simpleapp-jpa-module-visit</artifactId>
+</dependency>
+----
++
+The dependency on `<artifactId>simpleapp-jpa-module-petowner</artifactId>` can also be removed, due to transitivity.
+
+* and in the webapp's `ApplicationModule` Causeway module, add a corresponding import on `VisitModule`.
+
[source,java]
.ApplicationModule.java
----
+@Configuration
+@Import({
+ VisitModule.class,
+ SimpleModule.class,
+})
+@ComponentScan
+public class ApplicationModule {
+}
----
++
+The import on `PetOwnerModule.class` can be removed, due to transitivity.
+Run the application and check that it starts ok.
-
-[#exercise-5-2-visit-entitys-key-properties]
-== Ex 5.2: Visit entity's key properties
+[#exercise-5-3-visit-entitys-key-properties]
+== Ex 5.3: Visit entity's key properties
Now we have a visits module, we can now add in the `Visit` entity.
We'll start just with the key properties.
+=== Solution
[source,bash]
----
-git checkout tags/05-02-visit-entity-key-properties
+git checkout tags/05-03-visit-entity-key-properties
mvn clean install
mvn -pl spring-boot:run
----
-
=== Tasks
-* add a `Visit` entity, declaring the `pet` and `visitedAt` key properties:
+* add a `Visit` entity (in the `dom.visit` subpackage), declaring the `pet` and `visitAt` key properties:
+
[source,java]
.Visit.java
----
@Entity
@Table(
- schema="visits", // <.>
- name = "Visit",
- uniqueConstraints = {
- @UniqueConstraint(name = "Visit__pet_visitAt__UNQ", columnNames = {"owner_id", "name"})
- }
+ schema=VisitModule.SCHEMA,
+ uniqueConstraints = {
+ @UniqueConstraint(name = "Visit__pet_visitAt__UNQ", columnNames = {"pet_id", "visitAt"})
+ }
)
@EntityListeners(CausewayEntityListener.class)
-@Named("visits.Visit")
+@Named(VisitModule.NAMESPACE + ".Visit")
@DomainObject(entityChangePublishing = Publishing.ENABLED)
@DomainObjectLayout()
@NoArgsConstructor(access = AccessLevel.PUBLIC)
@@ -144,7 +202,6 @@ public class Visit implements Comparable<Visit> {
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", nullable = false)
@Getter @Setter
- @PropertyLayout(fieldSetId = "metadata", sequence = "1")
private Long id;
@Version
@@ -153,26 +210,27 @@ public class Visit implements Comparable<Visit> {
@Getter @Setter
private long version;
-
Visit(Pet pet, LocalDateTime visitAt) {
this.pet = pet;
this.visitAt = visitAt;
}
-
+ @ObjectSupport
public String title() {
- return titleService.titleOf(getPet()) + " @ " + getVisitAt().format(DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm"));
+ return titleService.titleOf(getPet()) // <.>
+ + " @ "
+ + getVisitAt().format(DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm"));
}
@ManyToOne(optional = false)
@JoinColumn(name = "pet_id")
- @PropertyLayout(fieldSetId = "name", sequence = "1")
+ @PropertyLayout(fieldSetId = "identity", sequence = "1")
@Getter @Setter
private Pet pet;
@Column(name = "visitAt", nullable = false)
@Getter @Setter
- @PropertyLayout(fieldSetId = "name", sequence = "2")
+ @PropertyLayout(fieldSetId = "identity", sequence = "2")
private LocalDateTime visitAt;
@@ -184,25 +242,134 @@ public class Visit implements Comparable<Visit> {
return comparator.compare(this, other);
}
- @Inject @Transient TitleService titleService;
+ @Inject @Transient TitleService titleService; // <1>
}
----
-<.> in the "visits" schema.
-Modules are vertical, cutting through the layers.
-Therefore the database schemas echo the Spring ``@Configuration``s and maven modules.
+<.> uses the injected xref:refguide:applib:index/services/title/TitleService.adoc[] to obtain the title of the object, as determined by the framework.
+
+* create a `Visit.layout.xml` layout file:
++
+[source,xml]
+.Visit.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>
+----
+
+* create a `Visit.columnOrder.txt` file
+
-Run the application, and confirm that the table is created correctly using menu:Prototyping[H2 Console].
+[source,text]
+.Visit.columnOrder.txt
+----
+pet
+visitAt
+#id
+#version
+----
+
+* download a `Visit.png` file.
+
+Run the application, and login as `secman-admin`/`pass` to confirm that the table is created correctly using menu:Prototyping[H2 Console].
+Also check that the unique index has been created correctly.
+
+image::05-02/Visit-entity.png[]
+
[#exercise-5-3-book-visit-action]
== Ex 5.3: "Book Visit" action
+We now want to extend our domain model so that ``Visit``s to be created.
+
+However, there's a problem:
+
+* we would like that behaviour to reside on `PetOwner` (say)
+* however the pet owner module doesn't know about visits.
+
+We can see this in the domain model:
+
+include::partial$domain.adoc[]
+
+Causeway's solution to this is to allow the visit module to define behaviour, but have the behaviour seem to belong to the `Pet` entity, at least so far as the user interface is concerned.
+This is done using a xref:userguide::mixins.adoc[].
+
+
+=== Solution
+
+[source,bash]
+----
+git checkout tags/05-03-book-visit-action
+mvn clean install
+mvn -pl spring-boot:run
+----
+
+
+
+=== Tasks
+
+
+
+[#exercise-5-4-capture-visit-reason]
+== Ex 5.3: Capture visit reason
+
In addition to the key properties, the `Visit` has one further mandatory property, `reason`.
This is required to be specified when a `Visit` is created ("what is the purpose of this visit?")
-In this exercise we'll add that additional property and use a mixin to allow ``Visit``s to be created.
+In this exercise we'll extend the "book visit" action to also capture that reason.
[source,bash]
@@ -322,10 +489,6 @@ public class Pet_bookVisit {
Also add in the UI files:
-* create a `Visit.layout.xml` layout file.
-
-* add a `Visit.png` file
-
* add a `Pet#visits.columnOrder.txt` file
+
to define which properties of Visit are visible as columns in ``Pet``'s `visits` collection.
diff --git a/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc b/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
index 25edb2f568..5ce9d43c8d 100644
--- a/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
@@ -9,6 +9,9 @@ validate pet name is unique within Pet
refactor addPet to be an inline-mixin.
+update home page, show upcoming appointments
+
+
visit
- need some words about adding VisitRepository
(causeway) 04/29: CAUSEWAY-2873: adds 03-06
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch 3.0.0
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 3e4e686d82731e26278fc139f33837cf21d113e8
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sat May 25 06:49:55 2024 +0100
CAUSEWAY-2873: adds 03-06
---
.../petclinic/pages/020-the-petclinic-domain.adoc | 4 +-
.../petclinic/pages/030-petowner-entity.adoc | 170 ++++++++++++++-------
2 files changed, 121 insertions(+), 53 deletions(-)
diff --git a/antora/components/tutorials/modules/petclinic/pages/020-the-petclinic-domain.adoc b/antora/components/tutorials/modules/petclinic/pages/020-the-petclinic-domain.adoc
index f7b58db22f..b301f60899 100644
--- a/antora/components/tutorials/modules/petclinic/pages/020-the-petclinic-domain.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/020-the-petclinic-domain.adoc
@@ -397,5 +397,5 @@ protected void execute(final ExecutionContext ec) {
Test that you can quickly create a whole set of `PetOwner` objects
from the "Prototyping" menu:
-* "Prototyping > Run Fixture Script" and select "Domain App Demo", or
-* "Prototyping > Recreate Objects and Return First".
+* menu:Prototyping[Run Fixture Script] and select "Domain App Demo", or
+* menu:Prototyping[Recreate Objects and Return First].
diff --git a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
index d51996df78..7e3eec8025 100644
--- a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
@@ -255,7 +255,7 @@ emailAddress
lastVisit
----
-Confirm that "Pet Owners > List" now shows the additional properties as columns.
+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 "Reload Changed Classes" menu item.
@@ -264,120 +264,188 @@ You can learn more about layout files xref:userguide::ui-layout-and-hints.adoc[h
-[#exercise-3-5-run-default-fixture-at-startup]
-== Ex 3.5: Run default fixture at startup
-Prototyping involves a lot of iteration, meaning make a change, re-run the app, in the app re-create some example objects, inspect the result.
+[#exercise-3-5-initial-fixture-script]
+== Ex 3.5: Initial Fixture Script
-One way to create objects is from the Prototyping menu; you might already have been doing this.
+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.
-xxx
=== Solution
[source,bash]
----
-git checkout tags/03-04-list-new-properties-of-PetOwner
+git checkout tags/03-05-initial-fixture-script
mvn clean install
mvn -pl spring-boot:run
----
+
=== 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 _will_ have 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); // <.>
+
+ // ...
+ }
+ // ...
+ }
+ // ...
+}
----
-*UP TO HERE*
+<.> 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.
-[#exercise-3-4-modify-the-menu-action-to-create-petowners]
-== Ex 3.4: Modify the menu action to create PetOwners
+When you now run the application you should now find that there are 10 `PetOwner` objects already created.
-If we want to create a new `PetOwner` and provide their `firstName`, at the moment it's a two stage process: create the `PetOwner` (using `PetOwners#create` action from the menu), then update their name (using the `updateName` action that we just looked at).
-In this exercise we'll simplify that workflow by allowing the `firstName` to optionally be specified during the initial create.
+[#exercise-3-6-update-home-page-to-show-PetOwners]
+== Ex 3.5: 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]
----
-git checkout tags/03-04-modifies-PetOwners-create-action
+git checkout tags/03-06-update-home-page-to-show-pet-owners
mvn clean install
mvn -pl spring-boot:run
----
+
=== Tasks
-* update `Orders#create` action, so that the end user can specify a `firstName` when creating a new `PetOwner`:
+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 also needs updating.
+
+* locate the `HomePageViewModel.layout.xml` file, and update accordingly
+
-[source,java]
-.PetOwners.java
-----
-@Action(semantics = SemanticsOf.NON_IDEMPOTENT)
-@ActionLayout(promptStyle = PromptStyle.DIALOG_SIDEBAR)
-public PetOwner create(
- @LastName final String lastName,
- @FirstName final String firstName) {
- return repositoryService.persist(PetOwner.withName(lastName, firstName));
-}
+[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`.
-=== Optional exercise
+We can change this by creating a file `HomePageViewModel#petOwners.columnOrder.txt`, alongside the `HomePageViewModel`:
-NOTE: If you decide to do this optional exercise, make the changes on a git branch so that you can resume with the main flow of exercises later.
+[source,txt]
+.HomePageViewModel#petOwners.columnOrder.txt
+----
+name
+telephoneNumber
+emailAddress
+#attachment
+#lastVisit
+#knownAs
+#version
+----
-It would be nice if the `PetOwner` were identified by both their `firstName` and their `lastName`; at the moment every `PetOwner` must have a unique `lastName`.
-Or, even better would be to introduce some sort of "customerNumber" and use this as the unique identifier.
+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-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.
+TODO: change the home page to list the pet owners by default.
-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.
+*UP TO HERE*
+
+[#exercise-3-4-modify-the-menu-action-to-create-petowners]
+== Ex 3.4: Modify the menu action to create PetOwners
+If we want to create a new `PetOwner` and provide their `firstName`, at the moment it's a two stage process: create the `PetOwner` (using `PetOwners#create` action from the menu), then update their name (using the `updateName` action that we just looked at).
+
+In this exercise we'll simplify that workflow by allowing the `firstName` to optionally be specified during the initial create.
=== Solution
[source,bash]
----
-git checkout tags/03-05-initial-fixture-script
+git checkout tags/03-04-modifies-PetOwners-create-action
mvn clean install
mvn -pl spring-boot:run
----
-
=== Tasks
-* create the following file in `src/main/resources` of the webapp (alongside the existing `application.yml` file):
-+
-[source,yaml]
-.application-dev.yaml
-----
-causeway:
- testing:
- fixtures:
- initial-script: petclinic.webapp.application.fixture.scenarios.PetClinicDemo
-----
-
-* modify the startup of your application to enable this profile, using this system prpoerty:
+* update `Orders#create` action, so that the end user can specify a `firstName` when creating a new `PetOwner`:
+
-[source]
+[source,java]
+.PetOwners.java
----
--Dspring.profiles.active=dev
+@Action(semantics = SemanticsOf.NON_IDEMPOTENT)
+@ActionLayout(promptStyle = PromptStyle.DIALOG_SIDEBAR)
+public PetOwner create(
+ @LastName final String lastName,
+ @FirstName final String firstName) {
+ return repositoryService.persist(PetOwner.withName(lastName, firstName));
+}
----
-When you run the application you should now find that there are 10 `PetOwner` objects already created.