/*
 *  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.
 */
package org.apache.causeway.core.config;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.net.URL;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import jakarta.activation.DataSource;
import jakarta.inject.Named;
import jakarta.persistence.Column;
import jakarta.validation.Constraint;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import jakarta.validation.Payload;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
import org.springframework.boot.info.BuildProperties;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.validation.annotation.Validated;

import org.apache.causeway.applib.CausewayModuleApplib;
import org.apache.causeway.applib.annotation.ActionLayout;
import org.apache.causeway.applib.annotation.DomainObject;
import org.apache.causeway.applib.annotation.DomainService;
import org.apache.causeway.applib.annotation.Introspection.IntrospectionPolicy;
import org.apache.causeway.applib.annotation.LabelPosition;
import org.apache.causeway.applib.annotation.PromptStyle;
import org.apache.causeway.applib.annotation.SemanticsOf;
import org.apache.causeway.applib.annotation.TableDecorator;
import org.apache.causeway.applib.query.Query;
import org.apache.causeway.applib.services.i18n.Mode;
import org.apache.causeway.applib.services.iactn.Execution;
import org.apache.causeway.applib.services.publishing.spi.CommandSubscriber;
import org.apache.causeway.applib.services.publishing.spi.EntityChangesSubscriber;
import org.apache.causeway.applib.services.publishing.spi.EntityPropertyChangeSubscriber;
import org.apache.causeway.applib.services.publishing.spi.ExecutionSubscriber;
import org.apache.causeway.applib.services.userreg.EmailNotificationService;
import org.apache.causeway.applib.services.userreg.UserRegistrationService;
import org.apache.causeway.applib.services.userui.UserMenu;
import org.apache.causeway.applib.value.semantics.TemporalValueSemantics.TemporalDisplayPattern;
import org.apache.causeway.applib.value.semantics.TemporalValueSemantics.TemporalEditingPattern;
import org.apache.causeway.commons.internal.base._NullSafe;
import org.apache.causeway.commons.internal.context._Context;
import org.apache.causeway.core.config.metamodel.facets.ActionConfigOptions;
import org.apache.causeway.core.config.metamodel.facets.AssociationLayoutConfigOptions;
import org.apache.causeway.core.config.metamodel.facets.AssociationLayoutConfigOptions.SequencePolicy;
import org.apache.causeway.core.config.metamodel.facets.CollectionLayoutConfigOptions;
import org.apache.causeway.core.config.metamodel.facets.DomainObjectConfigOptions;
import org.apache.causeway.core.config.metamodel.facets.ParameterConfigOptions;
import org.apache.causeway.core.config.metamodel.facets.PropertyConfigOptions;
import org.apache.causeway.core.config.metamodel.services.ApplicationFeaturesInitConfiguration;
import org.apache.causeway.core.config.metamodel.specloader.IntrospectionMode;
import org.apache.causeway.core.config.viewer.web.DialogMode;
import org.apache.causeway.core.config.viewer.web.TextMode;
import org.apache.causeway.schema.cmd.v2.ActionDto;
import org.apache.causeway.schema.cmd.v2.ParamDto;

import lombok.Builder;
import lombok.Data;
import lombok.Getter;

/**
 * Configuration 'beans' with meta-data (IDE-support).
 *
 * @see <a href="https://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html">spring.io</a>
 *
 * @since 2.0
 */
@ConfigurationProperties(CausewayConfiguration.ROOT_PREFIX)
@Data
@Validated
public class CausewayConfiguration {

    public static final String ROOT_PREFIX = "causeway";

    private final ConfigurableEnvironment environment;

    /**
     * To ensure that {@link #getBuildProperties()} is full populated, configure the <code>spring-boot-maven-plugin</code>
     * as follows:
     *
     * <pre>
     * &lt;plugin&gt;
     *     &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
     *     &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
     *     &lt;executions&gt;
     *         &lt;execution&gt;
     *             &lt;id&gt;build-info&lt;/id&gt;
     *             &lt;goals&gt;
     *                 &lt;goal&gt;build-info&lt;/goal&gt;
     *             &lt;/goals&gt;
     *             &lt;configuration&gt;
     *                 &lt;additionalProperties&gt;
     *                     &lt;java.version&gt;${java.version}&lt;/java.version&gt;
     *                     &lt;description&gt;${project.description}&lt;/description&gt;
     *                     ...
     *                 &lt;/additionalProperties&gt;
     *             &lt;/configuration&gt;
     *         &lt;/execution&gt;
     *     &lt;/executions&gt;
     *     ...
     * &lt;/plugin&gt;
     * </pre>
     */
    @Getter
    private final Optional<BuildProperties> buildProperties;

    @Builder // for testing
    @Autowired
    public CausewayConfiguration(final ConfigurableEnvironment environment, final Optional<BuildProperties> buildProperties) {
        this.environment = environment;
        this.buildProperties = buildProperties;
    }

    /**
     * All known configuration property names.
     *
     * <p>
     *     Or at least, from the {@link org.springframework.core.env.PropertySource} obtained from
     *     {@link ConfigurableEnvironment#getPropertySources()} that are also {@link EnumerablePropertySource}s.
     * </p>
     */
    public Stream<String> streamConfigurationPropertyNames() {
        MutablePropertySources propertySources = environment.getPropertySources();
        return StreamSupport
                .stream(propertySources.spliterator(), false)
                .filter(EnumerablePropertySource.class::isInstance)
                .map(EnumerablePropertySource.class::cast)
                .filter(ps->!"systemEnvironment".equalsIgnoreCase(ps.getName())) // exclude system env
                .map(EnumerablePropertySource::getPropertyNames)
                .flatMap(_NullSafe::stream);
    }

    /**
     * The value of a specific configuration property
     *
     * @param configurationPropertyName  - eg as obtained from {@link #streamConfigurationPropertyNames()}.
     */
    public Optional<String> valueOf(final String configurationPropertyName) {
        return Optional.ofNullable(environment.getProperty(configurationPropertyName));
    }

    private final Security security = new Security();
    @Data
    public static class Security {

        /**
         * If set, allows <i>Actions</i> with SAFE Semantics to be invoked with only VIEWING permissions.
         * <p>
         * default: false
         *
         * @see org.apache.causeway.applib.annotation.Action#semantics()
         * @see SemanticsOf
         */
        private boolean actionsWithSafeSemanticsRequireOnlyViewingPermission = false;

        private final Shiro shiro = new Shiro();
        @Data
        public static class Shiro {
            /**
             * If the Shiro subject is found to be still authenticated, then will be logged out anyway and then
             * re-authenticated.
             *
             * <p>
             * Applies only to the Restful Objects viewer.
             * </p>
             */
            private boolean autoLogoutIfAlreadyAuthenticated = false;

        }

        private final Spring spring = new Spring();
        @Data
        public static class Spring {
            /**
             * The framework on initialization by default disables any {@code CsrfFilter}(s) it finds
             * with <i>Spring Security</i> registered filters.
             * <p>
             * Setting this option to {@literal true} allows {@code CsrfFilter}(s) to be
             * configured. Yet EXPERIMENTAL.
             *
             * @see org.springframework.security.web.csrf.CsrfFilter
             * @see <a href="https://www.baeldung.com/spring-security-registered-filters">baeldung</a>
             */
            private boolean allowCsrfFilters = false;

        }

        private final Keycloak keycloak = new Keycloak();
        @Data
        public static class Keycloak {
            /**
             * The name of the realm for the Apache Causeway application, as configured in
             * Keycloak.
             */
            private String realm;

            /**
             * The base URL for the keycloak server.
             *
             * <p>
             *     For example, if running a keycloak using Docker container, such as:
             *     <pre>
             *         docker run -p 9090:8080 \
             *             -e KEYCLOAK_USER=admin \
             *             -e KEYCLOAK_PASSWORD=admin \
             *             quay.io/keycloak/keycloak:19.0.1
             *     </pre>,
             *
             *     then the URL would be "http://localhost:9090/auth".
             * </p>
             */
            private String baseUrl;

            /**
             * Specifies where users will be redirected after authenticating successfully if they
             * have not visited a secured page prior to authenticating or {@code alwaysUse} is
             * true.
             */
            private String loginSuccessUrl = "/wicket";

            /**
             * Whether to (attempt to) extract realm roles and copy into the <code>DefaultOidcUser</code>.
             *
             * <p>
             *     By default, realm roles are obtained from the token claims using the "User Realm Role" mapping type, into a token claim name "realm_access.roles"
             * </p>
             *
             * <p>
             *     This has been made a configuration option because some versions of Keycloak seemingly do not correctly extract these roles, see for example
             *     <a href="https://keycloak.discourse.group/t/resource-access-claim-missing-from-userinfo-until-i-change-the-name/1238/3">this discussion</a> and
             *     <a href="https://issues.redhat.com/browse/KEYCLOAK-9874">KEYCLOAK-9874</a>.
             * </p>
             */
            private boolean extractRealmRoles = true;

            /**
             * If {@link #isExtractRealmRoles() realm roles are to be extracted}, this allows the resultant role to be optionally prefixed.
             */
            private String realmRolePrefix = null;

            /**
             * Whether to (attempt to) extract client roles and copy into the <code>DefaultOidcUser</code>.
             *
             * <p>
             *     By default, client roles are extracted using the "User Client Role" mapping type, into a token claim name "resource_access.${client_id}.roles"
             * </p>
             *
             * <p>
             *     This has been made a configuration option because some versions of Keycloak seemingly do not correctly extract these roles, see for example
             *     <a href="https://keycloak.discourse.group/t/resource-access-claim-missing-from-userinfo-until-i-change-the-name/1238/3">this discussion</a> and
             *     <a href="https://issues.redhat.com/browse/KEYCLOAK-9874">KEYCLOAK-9874</a>.
             * </p>
             */
            private boolean extractClientRoles = true;
            /**
             * If {@link #isExtractClientRoles()}  client roles are to be extracted}, this allows the resultant role to be optionally prefixed.
             */
            private String clientRolePrefix = null;

            /**
             * Whether to (attempt to) extract any available roles and into the <code>DefaultOidcUser</code>.
             *
             * <p>
             *     This is to support any custom mapping type which maps into a token claim name called simply "roles"
             * </p>
             *
             * <p>
             *     This has been made a configuration option so that the workaround described in
             *     <a href="https://keycloak.discourse.group/t/resource-access-claim-missing-from-userinfo-until-i-change-the-name/1238/3">this discussion</a> and
             *     <a href="https://issues.redhat.com/browse/KEYCLOAK-9874">KEYCLOAK-9874</a> can be implemented.
             * </p>
             */
            private boolean extractRoles = false;
            /**
             * If {@link #isExtractRoles()}  roles are to be extracted}, this allows the resultant role to be optionally prefixed.
             */
            private String rolePrefix = null;
        }
    }

    private final Schema schema = new Schema();
    @Data
    public static class Schema {

        private final Command command = new Command();
        @Data
        public static class Command {

            public enum ParamIdentifierStrategy {
                BY_ID,
                /**
                 * For backward compatibility with v1 behaviour
                 */
                BY_CANONICAL_FRIENDLY_NAME;
            }

            /**
             * Whether the {@link ParamDto#getName()} field - which uniquely identifies a parameter within the
             * {@link org.apache.causeway.schema.cmd.v2.ActionDto action}'s
             * {@link ActionDto#getParameters() list of parameters} - is populated with the parameter's formal Id
             * (eg &quot;firstName&quot;) or instead using the parameter's friendly name (eg &quot;First Name&quot;).
             *
             * <p>
             *     The default is to use the {@link ParamIdentifierStrategy#BY_ID formal Id}, but the name is provided
             *     as an alternative for compatibility with v1.  Note that the name is potentially translated, so this
             *     could also cause issues within integration scenarios.
             * </p>
             */
            private ParamIdentifierStrategy paramIdentifierStrategy = ParamIdentifierStrategy.BY_ID;
        }
    }

    private final Applib applib = new Applib();
    @Data
    public static class Applib {

        private final Annotation annotation = new Annotation();
        @Data
        public static class Annotation {

            private final DomainObject domainObject = new DomainObject();

            public interface ConfigPropsForPropertyOrParameterLayout {
                /**
                 * Defines the default position for the label if not specified through an annotation.
                 *
                 * <p>
                 *     If left as {@link LabelPosition#NOT_SPECIFIED} and not overridden, then the position depends
                 *     upon the viewer implementation.
                 * </p>
                 */
                LabelPosition getLabelPosition();
            }

            @Data
            public static class DomainObject {

                /**
                 * The default for whether <i>domain entities</i> should be audited or not (meaning that any changes are
                 * sent through to {@link EntityChangesSubscriber}s and
                 * sent through to {@link EntityPropertyChangeSubscriber}.
                 *
                 * <p>
                 * This setting can be overridden on a case-by-case basis using {@link org.apache.causeway.applib.annotation.DomainObject#entityChangePublishing()}
                 * </p>
                 *
                 * <p>
                 *     Note: this applies only to domain entities, not view models.
                 * </p>
                 */
                private DomainObjectConfigOptions.EntityChangePublishingPolicy entityChangePublishing = DomainObjectConfigOptions.EntityChangePublishingPolicy.NONE;

                /**
                 * The default for whether the properties of domain objects can be edited, or whether instead they
                 * can be modified only using actions (or programmatically as a side-effect of actions on other objects).
                 *
                 * <p>
                 * This setting can be overridden on a case-by-case basis using {@link DomainObject#getEditing()  DomainObject#getEditing()}
                 * </p>
                 */
                private DomainObjectConfigOptions.EditingObjectsConfiguration editing = DomainObjectConfigOptions.EditingObjectsConfiguration.FALSE;

                private final CreatedLifecycleEvent createdLifecycleEvent = new CreatedLifecycleEvent();
                @Data
                public static class CreatedLifecycleEvent {
                    /**
                     * Influences whether an {@link org.apache.causeway.applib.events.lifecycle.ObjectCreatedEvent} should
                     * be published (on the internal {@link org.apache.causeway.applib.services.eventbus.EventBusService})
                     * whenever a domain object has been created using {@link org.apache.causeway.applib.services.factory.FactoryService}.
                     *
                     * <p>
                     *     The algorithm for determining whether (and what type of) an event is sent depends on the value of the
                     *     {@link org.apache.causeway.applib.annotation.DomainObject#createdLifecycleEvent() @DomainObject(createdLifecycleEvent=...)} for the
                     *     domain object in question.
                     * </p>
                     *
                     * <ul>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.lifecycle.ObjectCreatedEvent.Noop ObjectCreatedEvent.Noop},
                     *         then <i>no</i> event is sent.
                     *     </li>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.lifecycle.ObjectCreatedEvent.Default ObjectCreatedEvent.Default},
                     *         then an event is sent <i>if and only if</i> this configuration setting is set.
                     *     </li>
                     *     <li>
                     *         If set to any other subtype, then an event <i>is</i> sent.
                     *     </li>
                     * </ul>
                     */
                    private boolean postForDefault = true;
                }

                private final LoadedLifecycleEvent loadedLifecycleEvent = new LoadedLifecycleEvent();
                @Data
                public static class LoadedLifecycleEvent {
                    /**
                     * Influences whether an {@link org.apache.causeway.applib.events.lifecycle.ObjectLoadedEvent} should
                     * be published (on the internal {@link org.apache.causeway.applib.services.eventbus.EventBusService})
                     * whenever a domain <i>entity</i> has been loaded from the persistence store.
                     *
                     * <p>
                     *     The algorithm for determining whether (and what type of) an event is sent depends on the value of the
                     *     {@link org.apache.causeway.applib.annotation.DomainObject#loadedLifecycleEvent() @DomainObject(loadedLifecycleEvent=...)} for the
                     *     domain object in question.
                     * </p>
                     *
                     * <ul>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.lifecycle.ObjectLoadedEvent.Noop ObjectLoadedEvent.Noop},
                     *         then <i>no</i> event is sent.
                     *     </li>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.lifecycle.ObjectLoadedEvent.Default ObjectCreatedEvent.Default},
                     *         then an event is sent <i>if and only if</i> this configuration setting is set.
                     *     </li>
                     *     <li>
                     *         If set to any other subtype, then an event <i>is</i> sent.
                     *     </li>
                     * </ul>
                     *
                     * <p>
                     *     Note: this applies only to domain entities, not to view models.
                     * </p>
                     */
                    private boolean postForDefault = true;
                }

                private final PersistingLifecycleEvent persistingLifecycleEvent = new PersistingLifecycleEvent();
                @Data
                public static class PersistingLifecycleEvent {
                    /**
                     * Influences whether an {@link org.apache.causeway.applib.events.lifecycle.ObjectPersistingEvent} should
                     * be published (on the internal {@link org.apache.causeway.applib.services.eventbus.EventBusService})
                     * whenever a domain <i>entity</i> is about to be persisting (for the first time) to the persistence store.
                     *
                     * <p>
                     *     The algorithm for determining whether (and what type of) an event is sent depends on the value of the
                     *     {@link org.apache.causeway.applib.annotation.DomainObject#persistingLifecycleEvent() @DomainObject(persistingLifecycleEvent=...)} for the
                     *     domain object in question.
                     * </p>
                     *
                     * <ul>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.lifecycle.ObjectPersistingEvent.Noop ObjectPersistingEvent.Noop},
                     *         then <i>no</i> event is sent.
                     *     </li>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.lifecycle.ObjectPersistingEvent.Default ObjectCreatedEvent.Default},
                     *         then an event is sent <i>if and only if</i> this configuration setting is set.
                     *     </li>
                     *     <li>
                     *         If set to any other subtype, then an event <i>is</i> sent.
                     *     </li>
                     * </ul>
                     *
                     * <p>
                     *     Note: this applies only to domain entities, not to view models.
                     * </p>
                     */
                    private boolean postForDefault = true;
                }

                private final PersistedLifecycleEvent persistedLifecycleEvent = new PersistedLifecycleEvent();
                @Data
                public static class PersistedLifecycleEvent {
                    /**
                     * Influences whether an {@link org.apache.causeway.applib.events.lifecycle.ObjectPersistedEvent} should
                     * be published (on the internal {@link org.apache.causeway.applib.services.eventbus.EventBusService})
                     * whenever a domain <i>entity</i> has been persisted (for the first time) to the persistence store.
                     *
                     * <p>
                     *     The algorithm for determining whether (and what type of) an event is sent depends on the value of the
                     *     {@link org.apache.causeway.applib.annotation.DomainObject#persistedLifecycleEvent() @DomainObject(persistedLifecycleEvent=...)} for the
                     *     domain object in question.
                     * </p>
                     *
                     * <ul>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.lifecycle.ObjectPersistedEvent.Noop ObjectPersistedEvent.Noop},
                     *         then <i>no</i> event is sent.
                     *     </li>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.lifecycle.ObjectPersistedEvent.Default ObjectCreatedEvent.Default},
                     *         then an event is sent <i>if and only if</i> this configuration setting is set.
                     *     </li>
                     *     <li>
                     *         If set to any other subtype, then an event <i>is</i> sent.
                     *     </li>
                     * </ul>
                     *
                     * <p>
                     *     Note: this applies only to domain entities, not to view models.
                     * </p>
                     */
                    private boolean postForDefault = true;
                }

                private final RemovingLifecycleEvent removingLifecycleEvent = new RemovingLifecycleEvent();
                @Data
                public static class RemovingLifecycleEvent {
                    /**
                     * Influences whether an {@link org.apache.causeway.applib.events.lifecycle.ObjectRemovingEvent} should
                     * be published (on the internal {@link org.apache.causeway.applib.services.eventbus.EventBusService})
                     * whenever a persistent domain <i>entity</i> is about to be removed (that is, deleted)
                     * from the persistence store.
                     *
                     * <p>
                     *     The algorithm for determining whether (and what type of) an event is sent depends on the value of the
                     *     {@link org.apache.causeway.applib.annotation.DomainObject#removingLifecycleEvent() @DomainObject(removingLifecycleEvent=...)} for the
                     *     domain object in question.
                     * </p>
                     *
                     * <ul>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.lifecycle.ObjectRemovingEvent.Noop ObjectRemovingEvent.Noop},
                     *         then <i>no</i> event is sent.
                     *     </li>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.lifecycle.ObjectRemovingEvent.Default ObjectCreatedEvent.Default},
                     *         then an event is sent <i>if and only if</i> this configuration setting is set.
                     *     </li>
                     *     <li>
                     *         If set to any other subtype, then an event <i>is</i> sent.
                     *     </li>
                     * </ul>
                     *
                     * <p>
                     *     Note: this applies only to domain entities, not to view models.
                     * </p>
                     *
                     * <p>
                     *     Note: There is no corresponding <code>removed</code> callback, because (for the JDO persistence store at least)
                     *     it is not possible to interact with a domain entity once it has been deleted.
                     * </p>
                     */
                    private boolean postForDefault = true;
                }

                private final UpdatedLifecycleEvent updatedLifecycleEvent = new UpdatedLifecycleEvent();
                @Data
                public static class UpdatedLifecycleEvent {
                    /**
                     * Influences whether an {@link org.apache.causeway.applib.events.lifecycle.ObjectUpdatedEvent} should
                     * be published (on the internal {@link org.apache.causeway.applib.services.eventbus.EventBusService})
                     * whenever a persistent domain <i>entity</i> has been updated in the persistence store.
                     *
                     * <p>
                     *     The algorithm for determining whether (and what type of) an event is sent depends on the value of the
                     *     {@link org.apache.causeway.applib.annotation.DomainObject#updatedLifecycleEvent() @DomainObject(updatedLifecycleEvent=...)} for the
                     *     domain object in question.
                     * </p>
                     *
                     * <ul>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.lifecycle.ObjectUpdatedEvent.Noop ObjectUpdatedEvent.Noop},
                     *         then <i>no</i> event is sent.
                     *     </li>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.lifecycle.ObjectUpdatedEvent.Default ObjectCreatedEvent.Default},
                     *         then an event is sent <i>if and only if</i> this configuration setting is set.
                     *     </li>
                     *     <li>
                     *         If set to any other subtype, then an event <i>is</i> sent.
                     *     </li>
                     * </ul>
                     *
                     * <p>
                     *     Note: this applies only to domain entities, not to view models.
                     * </p>
                     */
                    private boolean postForDefault = true;
                }

                private final UpdatingLifecycleEvent updatingLifecycleEvent = new UpdatingLifecycleEvent();
                @Data
                public static class UpdatingLifecycleEvent {
                    /**
                     * Influences whether an {@link org.apache.causeway.applib.events.lifecycle.ObjectUpdatingEvent} should
                     * be published (on the internal {@link org.apache.causeway.applib.services.eventbus.EventBusService})
                     * whenever a persistent domain <i>entity</i> is about to be updated in the persistence store.
                     *
                     * <p>
                     *     The algorithm for determining whether (and what type of) an event is sent depends on the value of the
                     *     {@link org.apache.causeway.applib.annotation.DomainObject#updatingLifecycleEvent() @DomainObject(updatingLifecycleEvent=...)} for the
                     *     domain object in question.
                     * </p>
                     *
                     * <ul>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.lifecycle.ObjectUpdatingEvent.Noop ObjectUpdatingEvent.Noop},
                     *         then <i>no</i> event is sent.
                     *     </li>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.lifecycle.ObjectUpdatingEvent.Default ObjectCreatedEvent.Default},
                     *         then an event is sent <i>if and only if</i> this configuration setting is set.
                     *     </li>
                     *     <li>
                     *         If set to any other subtype, then an event <i>is</i> sent.
                     *     </li>
                     * </ul>
                     *
                     * <p>
                     *     Note: this applies only to domain entities, not to view models.
                     * </p>
                     */
                    private boolean postForDefault = true;
                }

            }

            private final DomainObjectLayout domainObjectLayout = new DomainObjectLayout();
            @Data
            public static class DomainObjectLayout {

                /**
                 * Defines the default number of objects that are shown in a &quot;standalone&quot; collection obtained as the
                 * result of invoking an action.
                 *
                 * <p>
                 *     This can be overridden on a case-by-case basis using {@link org.apache.causeway.applib.annotation.DomainObjectLayout#paged()}.
                 * </p>
                 */
                private int paged = 25;

                /**
                 * Defines whether the table representation of a standalone collection of this domain class should be
                 * decorated using a client-side Javascript library, eg for client-side paging and filtering.
                 */
                private Class<? extends TableDecorator> tableDecorator = TableDecorator.Default.class;

                private final CssClassUiEvent cssClassUiEvent = new CssClassUiEvent();
                @Data
                public static class CssClassUiEvent {
                    /**
                     * Influences whether an {@link org.apache.causeway.applib.events.ui.CssClassUiEvent} should
                     * be published (on the internal {@link org.apache.causeway.applib.services.eventbus.EventBusService})
                     * whenever a domain object is about to be rendered in the UI - thereby allowing subscribers to
                     * optionally {@link org.apache.causeway.applib.events.ui.CssClassUiEvent#setCssClass(String)} change)
                     * the CSS classes that are used.
                     *
                     * <p>
                     *     The algorithm for determining whether (and what type of) an event is sent depends on the value of the
                     *     {@link org.apache.causeway.applib.annotation.DomainObjectLayout#cssClassUiEvent()}  @DomainObjectLayout(cssClassEvent=...)} for the
                     *     domain object in question.
                     * </p>
                     *
                     * <ul>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.ui.CssClassUiEvent.Noop CssClassUiEvent.Noop},
                     *         then <i>no</i> event is sent.
                     *     </li>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.ui.CssClassUiEvent.Default CssClassUiEvent.Default},
                     *         then an event is sent <i>if and only if</i> this configuration setting is set.
                     *     </li>
                     *     <li>
                     *         If set to any other subtype, then an event <i>is</i> sent.
                     *     </li>
                     * </ul>
                     *
                     * <p>
                     *     The default is <tt>false</tt>, because otherwise the mere presence of <tt>@DomainObjectLayout</tt>
                     *     (perhaps for some attribute other than this one) will cause any imperative <code>cssClass()</code>
                     *     method to be ignored.
                     * </p>
                     */
                    private boolean postForDefault = false;
                }

                private final IconUiEvent iconUiEvent = new IconUiEvent();
                @Data
                public static class IconUiEvent {
                    /**
                     * Influences whether an {@link org.apache.causeway.applib.events.ui.IconUiEvent} should
                     * be published (on the internal {@link org.apache.causeway.applib.services.eventbus.EventBusService})
                     * whenever a domain object is about to be rendered in the UI - thereby allowing subscribers to
                     * optionally {@link org.apache.causeway.applib.events.ui.IconUiEvent#setIconName(String)} change)
                     * the icon that is used.
                     *
                     * <p>
                     *     The algorithm for determining whether (and what type of) an event is sent depends on the value of the
                     *     {@link org.apache.causeway.applib.annotation.DomainObjectLayout#iconUiEvent()}  @DomainObjectLayout(iconEvent=...)} for the
                     *     domain object in question.
                     * </p>
                     *
                     * <ul>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.ui.IconUiEvent.Noop IconUiEvent.Noop},
                     *         then <i>no</i> event is sent.
                     *     </li>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.ui.IconUiEvent.Default IconUiEvent.Default},
                     *         then an event is sent <i>if and only if</i> this configuration setting is set.
                     *     </li>
                     *     <li>
                     *         If set to any other subtype, then an event <i>is</i> sent.
                     *     </li>
                     * </ul>
                     *
                     * <p>
                     *     The default is <tt>false</tt>, because otherwise the mere presence of <tt>@DomainObjectLayout</tt>
                     *     (perhaps for some attribute other than this one) will cause any imperative <code>iconName()</code>
                     *     method to be ignored.
                     * </p>
                     */
                    private boolean postForDefault = false;
                }

                private final LayoutUiEvent layoutUiEvent = new LayoutUiEvent();
                @Data
                public static class LayoutUiEvent {
                    /**
                     * Influences whether an {@link org.apache.causeway.applib.events.ui.LayoutUiEvent} should
                     * be published (on the internal {@link org.apache.causeway.applib.services.eventbus.EventBusService})
                     * whenever a domain object is about to be rendered in the UI - thereby allowing subscribers to
                     * optionally {@link org.apache.causeway.applib.events.ui.LayoutUiEvent#setLayout(String)} change)
                     * the layout that is used.
                     *
                     * <p>
                     *     If a different layout value has been set, then a layout in the form <code>Xxx.layout-zzz.xml</code>
                     *     use used (where <code>zzz</code> is the name of the layout).
                     * </p>
                     *
                     * <p>
                     *     The algorithm for determining whether (and what type of) an event is sent depends on the value of the
                     *     {@link org.apache.causeway.applib.annotation.DomainObjectLayout#layoutUiEvent()}  @DomainObjectLayout(layoutEvent=...)} for the
                     *     domain object in question.
                     * </p>
                     *
                     * <ul>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.ui.LayoutUiEvent.Noop LayoutUiEvent.Noop},
                     *         then <i>no</i> event is sent.
                     *     </li>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.ui.LayoutUiEvent.Default LayoutUiEvent.Default},
                     *         then an event is sent <i>if and only if</i> this configuration setting is set.
                     *     </li>
                     *     <li>
                     *         If set to any other subtype, then an event <i>is</i> sent.
                     *     </li>
                     * </ul>
                     *
                     * <p>
                     *     The default is <tt>false</tt>, because otherwise the mere presence of <tt>@DomainObjectLayout</tt>
                     *     (perhaps for some attribute other than this one) will cause any imperative <code>layout()</code>
                     *     method to be ignored.
                     * </p>
                     */
                    private boolean postForDefault = false;
                }

                private final TitleUiEvent titleUiEvent = new TitleUiEvent();
                @Data
                public static class TitleUiEvent {
                    /**
                     * Influences whether an {@link org.apache.causeway.applib.events.ui.TitleUiEvent} should
                     * be published (on the internal {@link org.apache.causeway.applib.services.eventbus.EventBusService})
                     * whenever a domain object is about to be rendered in the UI - thereby allowing subscribers to
                     * optionally {@link org.apache.causeway.applib.events.ui.TitleUiEvent#setTitle(String)} change)
                     * the title that is used.
                     *
                     * <p>
                     *     The algorithm for determining whether (and what type of) an event is sent depends on the value of the
                     *     {@link org.apache.causeway.applib.annotation.DomainObjectLayout#titleUiEvent()}  @DomainObjectLayout(titleEvent=...)} for the
                     *     domain object in question.
                     * </p>
                     *
                     * <ul>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.ui.TitleUiEvent.Noop TitleUiEvent.Noop},
                     *         then <i>no</i> event is sent.
                     *     </li>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.ui.TitleUiEvent.Default TitleUiEvent.Default},
                     *         then an event is sent <i>if and only if</i> this configuration setting is set.
                     *     </li>
                     *     <li>
                     *         If set to any other subtype, then an event <i>is</i> sent.
                     *     </li>
                     * </ul>
                     *
                     * <p>
                     *     The default is <tt>false</tt>, because otherwise the mere presence of <tt>@DomainObjectLayout</tt>
                     *     (perhaps for some attribute other than this one) will cause any imperative <code>title()</code>
                     *     method to be ignored.
                     * </p>
                     */
                    private boolean postForDefault = false;
                }
            }

            private final Action action = new Action();
            @Data
            public static class Action {

                /**
                 * The default for whether action invocations should be reified
                 * as a {@link org.apache.causeway.applib.services.command.Command},
                 * to be sent to any registered
                 * {@link CommandSubscriber}s,
                 * typically for auditing purposes.
                 *
                 * <p>
                 *  This setting can be overridden on a case-by-case basis using
                 *  {@link org.apache.causeway.applib.annotation.Action#commandPublishing()}.
                 * </p>
                 */
                private ActionConfigOptions.PublishingPolicy commandPublishing = ActionConfigOptions.PublishingPolicy.NONE;

                /**
                 * The default for whether action invocations should be sent through to the
                 * {@link ExecutionSubscriber} for publishing.
                 *
                 * <p>
                 *     The service's {@link ExecutionSubscriber#onExecution(Execution) onExecution}
                 *     method is called only once per transaction, with
                 *     {@link Execution} collecting details of
                 *     the identity of the target object, the action invoked, the action arguments and the returned
                 *     object (if any).
                 * </p>
                 *
                 * <p>
                 *  This setting can be overridden on a case-by-case basis using {@link org.apache.causeway.applib.annotation.Action#executionPublishing()  Action#executionPublishing()}.
                 * </p>
                 */
                private ActionConfigOptions.PublishingPolicy executionPublishing = ActionConfigOptions.PublishingPolicy.NONE;

                private final DomainEvent domainEvent = new DomainEvent();
                @Data
                public static class DomainEvent {
                    /**
                     * Influences whether an {@link org.apache.causeway.applib.events.domain.ActionDomainEvent} should
                     * be published (on the internal {@link org.apache.causeway.applib.services.eventbus.EventBusService})
                     * whenever an action is being interacted with.
                     *
                     * <p>
                     *     Up to five different events can be fired during an interaction, with the event's
                     *     {@link org.apache.causeway.applib.events.domain.ActionDomainEvent#getEventPhase() phase}
                     *     determining which (hide, disable, validate, executing and executed).  Subscribers can
                     *     influence the behaviour at each of these phases.
                     * </p>
                     *
                     * <p>
                     *     The algorithm for determining whether (and what type of) an event is actually sent depends
                     *     on the value of the {@link org.apache.causeway.applib.annotation.Action#domainEvent()} for the
                     *     action in question
                     * </p>
                     *
                     * <ul>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.domain.ActionDomainEvent.Noop ActionDomainEvent.Noop},
                     *         then <i>no</i> event is sent.
                     *     </li>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.domain.ActionDomainEvent.Default ActionDomainEvent.Default},
                     *         then an event is sent <i>if and only if</i> this configuration setting is set.
                     *     </li>
                     *     <li>
                     *         If set to any other subtype, then an event <i>is</i> sent.
                     *     </li>
                     * </ul>
                     */
                    private boolean postForDefault = true;
                }

            }

            private final ActionLayout actionLayout = new ActionLayout();
            @Data
            public static class ActionLayout {

                private final CssClass cssClass = new CssClass();
                @Data
                public static class CssClass {
                    /**
                     * Provides a mapping of patterns to CSS classes, where the pattern is used to match against the
                     * name of the action method in order to determine a CSS class to use, for example on the action's
                     * button if rendered by the Wicket viewer.
                     *
                     * <p>
                     *     Providing a default set of patterns encourages a common set of verbs to be used.
                     * </p>
                     *
                     * <p>
                     *     The CSS class for individual actions can be overridden using
                     *     {@link org.apache.causeway.applib.annotation.ActionLayout#cssClass()}.
                     * </p>
                     */
                    private String[] patterns = {
                            "add.*:btn-info",
                            "remove.*:btn-warning",

                            "start.*:btn-info",
                            "play.*:btn-info",
                            "stop.*:btn-warning",

                            "reset.*:btn-warning",

                            "new.*:btn-info",
                            "create.*:btn-info",
                            "delete.*:btn-danger",

                            "verify.*:btn-success",
                            "decline.*:btn-danger",

                            "save.*:btn-success",

                            "approve.*:btn-success",
                            "reject.*:btn-danger",

                    };

                    @Getter(lazy = true)
                    private final Map<Pattern, String> patternsAsMap = asMap(getPatterns());

                }

                private final CssClassFa cssClassFa = new CssClassFa();
                @Data
                public static class CssClassFa {
                    /**
                     * Provides a mapping of patterns to font-awesome CSS classes, where the pattern is used to match
                     * against the name of the action method in order to determine a CSS class to use, for example on
                     * the action's menu icon if rendered by the Wicket viewer.
                     *
                     * <p>
                     *     Providing a default set of patterns encourages a common set of verbs to be used.
                     * </p>
                     *
                     * <p>
                     *     The font awesome class for individual actions can be overridden using
                     *     {@link org.apache.causeway.applib.annotation.ActionLayout#cssClassFa()}.
                     * </p>
                     */
                    private String[] patterns = {

                            "all.*:fa-solid fa-list",
                            "list.*:fa-solid fa-list",

                            "find.*:fa-search",
                            "lookup.*:fa-search",
                            "search.*:fa-search",

                            "send.*:fa-regular fa-paper-plane",

                            "open.*:fa-solid fa-arrow-up-right-from-square",
                            "close.*:fa-solid fa-regular fa-rectangle-xmark",

                            "recent.*:fa-solid fa-clock-rotate-left",

                            "lock.*:fa-solid fa-lock",
                            "unlock.*:fa-solid fa-unlock",

                            "permit.*:fa-solid fa-unlock",
                            "review.*:fa-solid fa-eye",

                            "add.*:fa-regular fa-square-plus",
                            "plus.*:fa-regular fa-square-plus",
                            "remove.*:fa-regular fa-square-minus",
                            "minus.*:fa-regular fa-square-minus",

                            "sign.*:fa-solid fa-signature",

                            "clear.*:fa-solid fa-broom",

                            "create.*:fa-regular fa-square-plus",
                            "new.*:fa-regular fa-square-plus",
                            "delete.*:fa-solid fa-trash",

                            "change.*:fa-regular fa-pen-to-square",
                            "edit.*:fa-regular fa-pen-to-square",
                            "maintain.*:fa-regular fa-pen-to-square",
                            "update.*:fa-regular fa-pen-to-square",

                            "cut.*:fa-solid fa-scissors",
                            "move.*:fa-solid fa-angles-right",
                            "copy.*:fa-regular fa-copy",
                            "duplicate.*:fa-solid fa-clone",
                            "clone.*:fa-solid fa-clone",
                            "categorise.*:fa-regular fa-folder-open",

                            "download.*:fa-solid fa-download",
                            "upload.*:fa-solid fa-upload",

                            "execute.*:fa-solid fa-bolt",
                            "run.*:fa-solid fa-bolt",
                            "trigger.*:fa-solid fa-bolt",

                            "link.*:fa-solid fa-link",
                            "unlink.*:fa-solid fa-link-slash",

                            "start.*:fa-solid fa-play",
                            "play.*:fa-solid fa-play",
                            "resume.*:fa-solid fa-play",
                            "pause.*:fa-solid fa-pause",
                            "suspend.*:fa-solid fa-pause",
                            "stop.*:fa-solid fa-stop",
                            "terminate.*:fa-solid fa-stop",

                            "previous.*:fa-backward-step",
                            "next.*:fa-forward-step",

                            "approve.*:fa-regular fa-thumbs-up",
                            "reject.*:fa-regular fa-thumbs-down",

                            "verify.*:fa-solid fa-check",
                            "decline.*:fa-solid fa-xmark",
                            "cancel.*:fa-solid fa-xmark",

                            "discard.*:fa-regular fa-trash-can",

                            "assign.*:fa-regular fa-hand-point-right",

                            "calculate.*:fa-calculator",

                            "import.*:fa-solid fa-file-import",
                            "export.*:fa-solid fa-file-export",

                            "first.*:fa-regular fa-star",

                            "install.*:fa-solid fa-wrench",

                            "setup.*:fa-solid fa-gear",
                            "configure.*:fa-solid fa-gear",

                            "refresh.*:fa-sync",
                            "renew.*:fa-rotate-right",
                            "reset.*:fa-rotate-left",

                            "save.*:fa-regular fa-floppy-disk",

                            "switch.*:fa-exchange",
                            "random.*:fa-shuffle",

                            "view.*:fa-regular fa-eye",

                            "wizard.*:fa-solid fa-wand-magic-sparkles"

                    };

                    @Getter(lazy = true)
                    private final Map<Pattern, String> patternsAsMap = asMap(getPatterns());

                }
            }

            private final Property property = new Property();
            @Data
            public static class Property {

                /**
                 * The default for whether property edits should be reified
                 * as a {@link org.apache.causeway.applib.services.command.Command},
                 * to be sent to any registered
                 * {@link CommandSubscriber}s,
                 * either for auditing or for replayed against a secondary
                 * system, eg for regression testing.
                 *
                 * <p>
                 *  This setting can be overridden on a case-by-case basis using
                 *  {@link org.apache.causeway.applib.annotation.Property#commandPublishing()}.
                 * </p>
                 */
                private PropertyConfigOptions.PublishingPolicy commandPublishing = PropertyConfigOptions.PublishingPolicy.NONE;

                /**
                 * The default for whether property edits should be sent through to the
                 * {@link ExecutionSubscriber} for publishing.
                 *
                 * <p>
                 * The service's {@link ExecutionSubscriber#onExecution(Execution)}  publish}
                 * method is called only once per transaction, with
                 * {@link Execution} collecting details of
                 * the identity of the target object, the property edited, and the new value of the property.
                 * </p>
                 *
                 * <p>
                 * This setting can be overridden on a case-by-case basis using
                 * {@link org.apache.causeway.applib.annotation.Property#executionPublishing()}.
                 * </p>
                 */
                private PropertyConfigOptions.PublishingPolicy executionPublishing = PropertyConfigOptions.PublishingPolicy.NONE;

                private final DomainEvent domainEvent = new DomainEvent();
                @Data
                public static class DomainEvent {
                    /**
                     * Influences whether an {@link org.apache.causeway.applib.events.domain.PropertyDomainEvent} should
                     * be published (on the internal {@link org.apache.causeway.applib.services.eventbus.EventBusService})
                     * whenever an property is being interacted with.
                     *
                     * <p>
                     *     Up to five different events can be fired during an interaction, with the event's
                     *     {@link org.apache.causeway.applib.events.domain.PropertyDomainEvent#getEventPhase() phase}
                     *     determining which (hide, disable, validate, executing and executed).  Subscribers can
                     *     influence the behaviour at each of these phases.
                     * </p>
                     *
                     * <p>
                     *     The algorithm for determining whether (and what type of) an event is actually sent depends
                     *     on the value of the {@link org.apache.causeway.applib.annotation.Property#domainEvent()} for the
                     *     property in question:
                     * </p>
                     *
                     * <ul>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.domain.PropertyDomainEvent.Noop propertyDomainEvent.Noop},
                     *         then <i>no</i> event is sent.
                     *     </li>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.domain.PropertyDomainEvent.Default propertyDomainEvent.Default},
                     *         then an event is sent <i>if and only if</i> this configuration setting is set.
                     *     </li>
                     *     <li>
                     *         If set to any other subtype, then an event <i>is</i> sent.
                     *     </li>
                     * </ul>
                     */
                    private boolean postForDefault = true;
                }

            }

            private final PropertyLayout propertyLayout = new PropertyLayout();
            @Data
            public static class PropertyLayout implements Applib.Annotation.ConfigPropsForPropertyOrParameterLayout {
                /**
                 * Defines the default position for the label for a domain object property.
                 *
                 * <p>
                 *     Can be overridden on a case-by-case basis using
                 *     {@link org.apache.causeway.applib.annotation.ParameterLayout#labelPosition()}.
                 * </p>
                 *
                 * <p>
                 *     If left as {@link LabelPosition#NOT_SPECIFIED} and not overridden, then the position depends
                 *     upon the viewer implementation.
                 * </p>
                 */
                private LabelPosition labelPosition = LabelPosition.NOT_SPECIFIED;

                /**
                 * How {@link org.apache.causeway.applib.annotation.PropertyLayout#sequence()}
                 * should be handled when calculating the slot-in order for unreferenced <i>Properties</i>.
                 * {@code AS_PER_SEQUENCE} will use Dewey order based on available 'sequence' attributes,
                 * whereas {@code ALPHABETICALLY} will use alphabetical order based on member names.
                 * <p>
                 * default: {@code AS_PER_SEQUENCE}
                 */
                private AssociationLayoutConfigOptions.SequencePolicy sequencePolicyIfUnreferenced = SequencePolicy.AS_PER_SEQUENCE;
            }

            private final Collection collection = new Collection();
            @Data
            public static class Collection {

                private final DomainEvent domainEvent = new DomainEvent();
                @Data
                public static class DomainEvent {
                    /**
                     * Influences whether an {@link org.apache.causeway.applib.events.domain.CollectionDomainEvent} should
                     * be published (on the internal {@link org.apache.causeway.applib.services.eventbus.EventBusService})
                     * whenever a collection is being interacted with.
                     *
                     * <p>
                     *     Up to two different events can be fired during an interaction, with the event's
                     *     {@link org.apache.causeway.applib.events.domain.CollectionDomainEvent#getEventPhase() phase}
                     *     determining which (hide, disable)Subscribers can influence the behaviour at each of these
                     *     phases.
                     * </p>
                     *
                     * <p>
                     *     The algorithm for determining whether (and what type of) an event is actually sent depends
                     *     on the value of the {@link org.apache.causeway.applib.annotation.Collection#domainEvent()} for the
                     *     collection action in question:
                     * </p>
                     *
                     * <ul>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.domain.CollectionDomainEvent.Noop CollectionDomainEvent.Noop},
                     *         then <i>no</i> event is sent.
                     *     </li>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.domain.CollectionDomainEvent.Default CollectionDomainEvent.Default},
                     *         then an event is sent <i>if and only if</i> this configuration setting is set.
                     *     </li>
                     *     <li>
                     *         If set to any other subtype, then an event <i>is</i> sent.
                     *     </li>
                     * </ul>
                     */
                    private boolean postForDefault = true;
                }
            }

            private final CollectionLayout collectionLayout = new CollectionLayout();
            @Data
            public static class CollectionLayout {

                /**
                 * Defines the initial view to display collections when rendered.
                 *
                 * <p>
                 *     The value of this can be overridden on a case-by-case basis using
                 *     {@link org.apache.causeway.applib.annotation.CollectionLayout#defaultView()}.
                 *     Note that this default configuration property is an enum and so defines only a fixed number of
                 *     values, whereas the annotation returns a string; this is to allow for flexibility that
                 *     individual viewers might support their own additional types.  For example, the Wicket viewer
                 *     supports <codefullcalendar</code> which can render objects that have a date on top of a calendar
                 *     view.
                 * </p>
                 */
                private CollectionLayoutConfigOptions.DefaultView defaultView = CollectionLayoutConfigOptions.DefaultView.TABLE;

                /**
                 * Defines the default number of objects that are shown in a &quot;parented&quot; collection of a
                 * domain object,
                 * result of invoking an action.
                 *
                 * <p>
                 *     This can be overridden on a case-by-case basis using
                 *     {@link org.apache.causeway.applib.annotation.CollectionLayout#paged()}.
                 * </p>
                 */
                private int paged = 12;

                /**
                 * How {@link org.apache.causeway.applib.annotation.CollectionLayout#sequence()}
                 * should be handled when calculating the slot-in order for unreferenced <i>Collections</i>.
                 * {@code AS_PER_SEQUENCE} will use Dewey order based on available 'sequence' attributes,
                 * whereas {@code ALPHABETICALLY} will use alphabetical order based on member names.
                 * <p>
                 * default: {@code AS_PER_SEQUENCE}
                 */
                private AssociationLayoutConfigOptions.SequencePolicy sequencePolicyIfUnreferenced =
                        SequencePolicy.AS_PER_SEQUENCE;

                /**
                 * Defines whether the table representation of a collection should be decorated using a client-side
                 * Javascript library, eg for client-side paging and filtering.
                 */
                private Class<? extends TableDecorator> tableDecorator = TableDecorator.Default.class;

            }

            private final ViewModel viewModel = new ViewModel();
            @Data
            public static class ViewModel {
                private final Validation validation = new Validation();
                @Data
                public static class Validation {
                    private final SemanticChecking semanticChecking = new SemanticChecking();
                    @Data
                    public static class SemanticChecking {
                        /**
                         * Whether to check for inconsistencies between the usage of
                         * {@link org.apache.causeway.applib.annotation.DomainObject} and
                         * {@link org.apache.causeway.applib.annotation.DomainObjectLayout}.
                         */
                        private boolean enable = false;
                    }
                }
            }

            private final ViewModelLayout viewModelLayout = new ViewModelLayout();
            @Data
            public static class ViewModelLayout {

                private final CssClassUiEvent cssClassUiEvent = new CssClassUiEvent();
                @Data
                public static class CssClassUiEvent {
                    /**
                     * Influences whether an {@link org.apache.causeway.applib.events.ui.CssClassUiEvent} should
                     * be published (on the internal {@link org.apache.causeway.applib.services.eventbus.EventBusService})
                     * whenever a view model (annotated with
                     * {@link org.apache.causeway.applib.annotation.DomainObject#nature() @DomainObject#nature} of
                     * {@link org.apache.causeway.applib.annotation.Nature#VIEW_MODEL}) is about to be rendered in the
                     * UI - thereby allowing subscribers to optionally
                     * {@link org.apache.causeway.applib.events.ui.CssClassUiEvent#setCssClass(String)} change) the CSS
                     * classes that are used.
                     *
                     * <p>
                     *     The algorithm for determining whether (and what type of) an event is sent depends on the value of the
                     *     {@link org.apache.causeway.applib.annotation.DomainObjectLayout#cssClassUiEvent() @DomainObjectLayout(cssClassEvent=...)}
                     *     for the domain object in question:
                     * </p>
                     *
                     * <ul>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.ui.CssClassUiEvent.Noop CssClassUiEvent.Noop},
                     *         then <i>no</i> event is sent.
                     *     </li>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.ui.CssClassUiEvent.Default CssClassUiEvent.Default},
                     *         then an event is sent <i>if and only if</i> this configuration setting is set.
                     *     </li>
                     *     <li>
                     *         If set to any other subtype, then an event <i>is</i> sent.
                     *     </li>
                     * </ul>
                     */
                    private boolean postForDefault =true;
                }

                private final IconUiEvent iconUiEvent = new IconUiEvent();
                @Data
                public static class IconUiEvent {
                    /**
                     * Influences whether an {@link org.apache.causeway.applib.events.ui.IconUiEvent} should
                     * be published (on the internal {@link org.apache.causeway.applib.services.eventbus.EventBusService})
                     * whenever a view model (annotated with
                     * {@link org.apache.causeway.applib.annotation.DomainObject#nature() @DomainObject#nature} of
                     * {@link org.apache.causeway.applib.annotation.Nature#VIEW_MODEL})  is about to be rendered in the
                     * UI - thereby allowing subscribers to optionally
                     * {@link org.apache.causeway.applib.events.ui.IconUiEvent#setIconName(String)} change) the icon that
                     * is used.
                     *
                     * <p>
                     *     The algorithm for determining whether (and what type of) an event is sent depends on the value of the
                     *     {@link org.apache.causeway.applib.annotation.DomainObjectLayout#iconUiEvent() @ViewModelLayout(iconEvent=...)}
                     *     for the domain object in question:
                     * </p>
                     *
                     * <ul>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.ui.IconUiEvent.Noop IconUiEvent.Noop},
                     *         then <i>no</i> event is sent.
                     *     </li>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.ui.IconUiEvent.Default IconUiEvent.Default},
                     *         then an event is sent <i>if and only if</i> this configuration setting is set.
                     *     </li>
                     *     <li>
                     *         If set to any other subtype, then an event <i>is</i> sent.
                     *     </li>
                     * </ul>
                     */
                    private boolean postForDefault =true;
                }

                private final LayoutUiEvent layoutUiEvent = new LayoutUiEvent();
                @Data
                public static class LayoutUiEvent {
                    /**
                     * Influences whether an {@link org.apache.causeway.applib.events.ui.LayoutUiEvent} should
                     * be published (on the internal {@link org.apache.causeway.applib.services.eventbus.EventBusService})
                     * whenever a view model (annotated with
                     * {@link org.apache.causeway.applib.annotation.DomainObject#nature() @DomainObject#nature} of
                     * {@link org.apache.causeway.applib.annotation.Nature#VIEW_MODEL})  is about to be rendered in the
                     * UI - thereby allowing subscribers to optionally
                     * {@link org.apache.causeway.applib.events.ui.LayoutUiEvent#setLayout(String)} change) the layout that is used.
                     *
                     * <p>
                     *     If a different layout value has been set, then a layout in the form <code>Xxx.layout-zzz.xml</code>
                     *     use used (where <code>zzz</code> is the name of the layout).
                     * </p>
                     *
                     * <p>
                     *     The algorithm for determining whether (and what type of) an event is sent depends on the value of the
                     *     {@link org.apache.causeway.applib.annotation.DomainObjectLayout#layoutUiEvent() @DomainObjectLayout(layoutEvent=...)}
                     *     for the domain object in question:
                     * </p>
                     *
                     * <ul>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.ui.LayoutUiEvent.Noop LayoutUiEvent.Noop},
                     *         then <i>no</i> event is sent.
                     *     </li>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.ui.LayoutUiEvent.Default LayoutUiEvent.Default},
                     *         then an event is sent <i>if and only if</i> this configuration setting is set.
                     *     </li>
                     *     <li>
                     *         If set to any other subtype, then an event <i>is</i> sent.
                     *     </li>
                     * </ul>
                     */
                    private boolean postForDefault =true;
                }

                private final TitleUiEvent titleUiEvent = new TitleUiEvent();
                @Data
                public static class TitleUiEvent {
                    /**
                     * Influences whether an {@link org.apache.causeway.applib.events.ui.TitleUiEvent} should
                     * be published (on the internal {@link org.apache.causeway.applib.services.eventbus.EventBusService})
                     * whenever a view model (annotated with
                     * {@link org.apache.causeway.applib.annotation.DomainObject#nature() @DomainObject#nature} of
                     * {@link org.apache.causeway.applib.annotation.Nature#VIEW_MODEL})  is about to be rendered in the
                     * UI - thereby allowing subscribers to
                     * optionally {@link org.apache.causeway.applib.events.ui.TitleUiEvent#setTitle(String)} change)
                     * the title that is used.
                     *
                     * <p>
                     *     The algorithm for determining whether (and what type of) an event is sent depends on the value of the
                     *     {@link org.apache.causeway.applib.annotation.DomainObjectLayout#titleUiEvent() @DomainObjectLayout(titleEvent=...)} for the
                     *     domain object in question:
                     * </p>
                     *
                     * <ul>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.ui.TitleUiEvent.Noop TitleUiEvent.Noop},
                     *         then <i>no</i> event is sent.
                     *     </li>
                     *     <li>
                     *         If set to some subtype of
                     *         {@link org.apache.causeway.applib.events.ui.TitleUiEvent.Default TitleUiEvent.Default},
                     *         then an event is sent <i>if and only if</i> this configuration setting is set.
                     *     </li>
                     *     <li>
                     *         If set to any other subtype, then an event <i>is</i> sent.
                     *     </li>
                     * </ul>
                     */
                    private boolean postForDefault =true;
                }
            }

            private final Parameter parameter = new Parameter();
            @Data
            public static class Parameter {

                /**
                 * Whether parameters should be reset to their default if an earlier parameter changes its
                 * value, or whether instead a parameter value, once changed by the end-user, should never be
                 * overwritten even if the end-user changes an earlier parameter value.
                 *
                 * <p>
                 *     This setting can be overridden on a case-by-case basis using
                 *     {@link org.apache.causeway.applib.annotation.Parameter#precedingParamsPolicy() Parameter#precedingParametersPolicy()}.
                 * </p>
                 */
                private ParameterConfigOptions.PrecedingParametersPolicy precedingParametersPolicy = ParameterConfigOptions.PrecedingParametersPolicy.RESET;

            }

            private final ParameterLayout parameterLayout = new ParameterLayout();
            @Data
            public static class ParameterLayout implements Applib.Annotation.ConfigPropsForPropertyOrParameterLayout {
                /**
                 * Defines the default position for the label for an action parameter.
                 *
                 * <p>
                 *     Can be overridden on a case-by-case basis using
                 *     {@link org.apache.causeway.applib.annotation.ParameterLayout#labelPosition()}.
                 * </p>
                 *
                 * <p>
                 *     If left as {@link LabelPosition#NOT_SPECIFIED} and not overridden, then the position depends
                 *     upon the viewer implementation.
                 * </p>
                 */
                private LabelPosition labelPosition = LabelPosition.NOT_SPECIFIED;
            }

        }

        private final Service service = new Service();
        @Data
        public static class Service {

            private final MetricsService metricsService = new MetricsService();
            @Data
            public static class MetricsService {
                public enum Level {
                    COUNTERS_ONLY,
                    COUNTERS_AND_DETAIL;

                    public boolean isCountersOnly() { return this == COUNTERS_ONLY; }
                    public boolean isCountersAndDetail() { return this == COUNTERS_AND_DETAIL; }
                }

                /**
                 * What level of detail the MetricsService should capture.
                 */
                private Level level = Level.COUNTERS_ONLY;
            }
        }
    }

    private final Core core = new Core();
    @Data
    public static class Core {

        private final Config config = new Config();
        @Data
        public static class Config {

            public static enum ConfigurationPropertyVisibilityPolicy {
                NEVER_SHOW,
                SHOW_ONLY_IN_PROTOTYPE,
                ALWAYS_SHOW
            }

            /**
             * Configuration values might contain sensitive data, hence per default,
             * configuration properties are only visible with the configuration-page
             * when <i>prototyping</i>.
             *
             * <p>
             * Alternatively this policy can be set to either <b>always</b> show or <b>never</b> show.
             * </p>
             *
             * @see ConfigurationPropertyVisibilityPolicy
             */
            private ConfigurationPropertyVisibilityPolicy configurationPropertyVisibilityPolicy
                    = ConfigurationPropertyVisibilityPolicy.SHOW_ONLY_IN_PROTOTYPE;

        }

        private final MetaModel metaModel = new MetaModel();
        @Data
        public static class MetaModel {

            /**
             * Whether domain objects to which the current user does not have visibility access should be rendered
             * within collections or drop-down choices/autocompletes.
             *
             * <p>
             *     One reason this filtering may be necessary is for multi-tenanted applications, whereby an end-user
             *     should only be able to "see" what data that they own.  For efficiency, the application should
             *     only query for objects that the end-user owns.  This configuration property acts as a safety net to
             *     prevent the end-user from viewing domain objects <i>even if</i> those domain objects were rehydrated
             *     from the persistence store.
             * </p>
             */
            private boolean filterVisibility = true;

            private final ProgrammingModel programmingModel = new ProgrammingModel();
            @Data
            public static class ProgrammingModel {

                /**
                 * If set, then any aspects of the programming model (as implemented by <code>FacetFactory</code>s that
                 * have been indicated as deprecated will simply be ignored/excluded from the metamodel.
                 */
                private boolean ignoreDeprecated = false;
            }

            private final Introspector introspector = new Introspector();
            @Data
            public static class Introspector {

                /**
                 * Policy as to how introspection should process
                 * class members and supporting methods.
                 * <p>
                 * Default is to only introspect public class members, while annotating these is optional.
                 */
                private IntrospectionPolicy policy;
                public IntrospectionPolicy getPolicy() {
                    return Optional.ofNullable(policy)
                            .orElse(IntrospectionPolicy.ANNOTATION_OPTIONAL);
                }

                /**
                 * Whether to perform metamodel introspection in parallel, intended to speed up bootstrapping.
                 *
                 * <p>
                 *     For now this is <i>experimental</i>.
                 *     We recommend this is left as disabled (the default).
                 * </p>
                 */
                private boolean parallelize = false; //TODO[CAUSEWAY-2382] concurrent spec-loading is experimental

                /**
                 * Whether all known types should be fully introspected as part of the bootstrapping, or should only be
                 * partially introspected initially.
                 *
                 * <p>
                 * Leaving this as lazy means that there's a chance that metamodel validation errors will not be
                 * discovered during bootstrap.  That said, metamodel validation is still run incrementally for any
                 * classes introspected lazily after initial bootstrapping (unless {@link #isValidateIncrementally()} is
                 * disabled.
                 * </p>
                 */
                private IntrospectionMode mode = IntrospectionMode.LAZY_UNLESS_PRODUCTION;

                /**
                 * If true, then no new specifications will be allowed to be loaded once introspection has been complete.
                 *
                 * <p>
                 * Only applies if the introspector is configured to perform full introspection up-front (either because of
                 * {@link IntrospectionMode#FULL} or {@link IntrospectionMode#LAZY_UNLESS_PRODUCTION} when in production);
                 * otherwise is ignored.
                 * </p>
                 */
                private boolean lockAfterFullIntrospection = true;

                /**
                 * If true, then metamodel validation is performed after any new specification has been loaded (after the
                 * initial bootstrapping).
                 *
                 * <p>
                 * This does <i>not</i> apply if the introspector is configured to perform full introspection up-front
                 * AND when the metamodel is {@link Core.MetaModel.Introspector#isLockAfterFullIntrospection() locked} after initial bootstrapping
                 * (because in that case the lock check will simply prevent any new specs from being loaded).
                 * But it will apply otherwise.
                 * </p>
                 *
                 * <p>In particular, this setting <i>can</i> still apply even if the {@link Core.MetaModel.Introspector#getMode() introspection mode}
                 * is set to {@link IntrospectionMode#FULL full}, because that in itself does not preclude some code
                 * from attempting to load some previously unknown type.  For example, a fixture script could attempt to
                 * invoke an action on some new type using the
                 * {@link org.apache.causeway.applib.services.wrapper.WrapperFactory} - this will cause introspection of that
                 * new type to be performed.
                 * </p>
                 */
                private boolean validateIncrementally = true;

            }

            private final Validator validator = new Validator();
            @Data
            public static class Validator {

                /**
                 * Whether to perform metamodel validation in parallel.
                 */
                private boolean parallelize = true;

                /**
                 * This setting is used to determine whether the use of such deprecated features is
                 * allowed.
                 *
                 * <p>
                 *     If not allowed, then metamodel validation errors will be flagged.
                 * </p>
                 *
                 * <p>
                 *     Note that this settings has no effect if the programming model has been configured to
                 *     {@link ProgrammingModel#isIgnoreDeprecated() ignore deprecated} features (because in this case
                 *     the programming model facets simply won't be included in the introspection process.
                 * </p>
                 */
                private boolean allowDeprecated = true;

                /**
                 * Whether to validate that any actions that accept action parameters have either a corresponding
                 * choices or auto-complete for that action parameter, or are associated with a collection of the
                 * appropriate type.
                 */
                private boolean actionCollectionParameterChoices = true;

                /**
                 * Whether to ensure that the logical-type-name of all objects must be specified explicitly, using either
                 * {@link Named}.
                 *
                 * <p>
                 *     It is <i>highly advisable</i> to leave this set as enabled (the default). These logical-type-names
                 *     should also (of course) be unique - among non-abstract types.
                 * </p>
                 */
                private boolean explicitLogicalTypeNames = false;

                /**
                 * Allows logical type name in {@link Named} also be included in the list of {@link DomainObject#aliased()}
                 * or {@link DomainService#aliased()}.
                 *
                 * <p>
                 *     It is <i>highly advisable</i> to leave this disabled. This option is meant as a practical way to
                 *     enable to transition from old names to new logical type names. Especially when you have a large
                 *     number of files that have to migrated and you want to do the migration in incremental steps.
                 * </p>
                 */
                private boolean allowLogicalTypeNameAsAlias = false;

                private final JaxbViewModel jaxbViewModel = new JaxbViewModel();
                @Data
                public static class JaxbViewModel {
                    /**
                     * If set, then ensures that all JAXB-style view models are concrete classes, not abstract.
                     */
                    private boolean notAbstract = true;
                    /**
                     * If set, then ensures that all JAXB-style view models are either top-level classes or nested
                     * static classes (in other words, checks that they are not anonymous, local nor nested
                     * non-static classes).
                     */
                    private boolean notInnerClass = true;
                    /**
                     * If set, then ensures that all JAXB-style view models have a no-arg constructor.
                     */
                    private boolean noArgConstructor = false;
                    /**
                     * If set, then ensures that for all properties of JAXB-style view models where the property's type
                     * is an entity, then that entity's type has been correctly annotated with
                     * @{@link jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter} (so that the property's value can
                     * be converted into a serializable form).
                     */
                    private boolean referenceTypeAdapter = true;
                    /**
                     * If set, then ensures that for all properties of JAXB-style view models where the property's type
                     * is a date or time, then that property has been correctly annotated with
                     * @{@link jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter} (so that the property's value can
                     * be converted into a serializable form).
                     */
                    private boolean dateTimeTypeAdapter = true;
                }

                private final Jdoql jdoql = new Jdoql();
                @Data
                public static class Jdoql {
                    /**
                     * If set, then ensures that the 'FROM' clause within any JDOQL <code>@Query</code>s annotations
                     * relates to a known entity type, and moreover that that type is compatible with the type on
                     * which the annotation appears: meaning its either a supertype of or the same type as the
                     * annotated type.
                     */
                    private boolean fromClause = true;
                    /**
                     * If set, then ensures that the 'VARIABLES' clause within any JDOQL <code>@Query</code>s relates
                     * to a known entity type.
                     */
                    private boolean variablesClause = true;
                }
            }
        }

        private final Runtime runtime = new Runtime();
        @Data
        public static class Runtime {

            /**
             * If set, then overrides the application's {@link Locale#getDefault()}
             */
            private Optional<String> locale = Optional.empty();

            /**
             * If set, then override's the application's timezone.
             */
            private String timezone;

        }

        private final RuntimeServices runtimeServices = new RuntimeServices();
        @Data
        public static class RuntimeServices {

            private final Email email = new Email();
            @Data
            public static class Email {

                @Deprecated
                private int port = 587;

                /**
                 * The port to use for sending email.
                 *
                 * @deprecated  - ignored, instead use <code>spring.mail.port</code>
                 */
                @Deprecated
                @DeprecatedConfigurationProperty(replacement = "spring.mail.port")
                public int getPort() {
                    return port;
                }

                @Deprecated
                private int socketConnectionTimeout = 2000;

                /**
                 * The maximum number of millseconds to wait to obtain a socket connection before timing out.
                 *
                 * @deprecated  - ignored, instead use <code>spring.mail.properties.mail.smtp.connectiontimeout</code>
                 */
                @Deprecated
                @DeprecatedConfigurationProperty(replacement = "spring.mail.properties.mail.smtp.connectiontimeout")
                public int getSocketConnectionTimeout() {
                    return socketConnectionTimeout;
                }

                @Deprecated
                private int socketTimeout = 2000;

                /**
                 * The maximum number of milliseconds to wait to obtain a socket before timing out.
                 *
                 * @deprecated  - ignored, instead use <code>spring.mail.properties.mail.smtp.timeout</code>
                 */
                @Deprecated
                @DeprecatedConfigurationProperty(replacement = "spring.mail.properties.mail.smtp.timeout")
                public int getSocketTimeout() {
                    return socketTimeout;
                }

                /**
                 * If an email fails to send, whether to propagate the exception (meaning that potentially the end-user
                 * might see the exception), or whether instead to just indicate failure through the return value of
                 * the method ({@link org.apache.causeway.applib.services.email.EmailService#send(List, List, List, String, String, DataSource...)}
                 * that's being called.
                 */
                private boolean throwExceptionOnFail = true;

                private final Override override = new Override();
                @Data
                public static class Override {
                    /**
                     * Intended for testing purposes only, if set then the requested <code>to:</code> of the email will
                     * be ignored, and instead sent to this email address instead.
                     */
                    @jakarta.validation.constraints.Email
                    private String to;
                    /**
                     * Intended for testing purposes only, if set then the requested <code>cc:</code> of the email will
                     * be ignored, and instead sent to this email address instead.
                     */
                    @jakarta.validation.constraints.Email
                    private String cc;
                    /**
                     * Intended for testing purposes only, if set then the requested <code>bcc:</code> of the email will
                     * be ignored, and instead sent to this email address instead.
                     */
                    @jakarta.validation.constraints.Email
                    private String bcc;
                }

                private final Sender sender = new Sender();
                @Data
                public static class Sender {

                    @Deprecated
                    private String hostname;

                    /**
                     * Specifies the host running the SMTP service.
                     *
                     * <p>
                     *     If not specified, then the value used depends upon the email implementation.  The default
                     *     implementation will use the <code>mail.smtp.host</code> system property.
                     * </p>
                     *
                     * @deprecated - now ignored, instead use <code>spring.mail.host</code>
                     */
                    @Deprecated
                    @DeprecatedConfigurationProperty(replacement = "spring.mail.host")
                    public String getHostname() {
                        return hostname;
                    }

                    @Deprecated
                    private String username;

                    /**
                     * Specifies the username to use to connect to the SMTP service.
                     *
                     * <p>
                     *     If not specified, then the sender's {@link #getAddress() email address} will be used instead.
                     * </p>
                     *
                     * @deprecated - now ignored, instead use <code>spring.mail.username</code>
                     */
                    @Deprecated
                    @DeprecatedConfigurationProperty(replacement = "spring.mail.username")
                    public String getUsername() {
                        return username;
                    }

                    @Deprecated
                    private String password;

                    /**
                     * Specifies the password (corresponding to the {@link #getUsername() username} to connect to the
                     * SMTP service.
                     *
                     * <p>
                     *     This configuration property is mandatory (for the default implementation of the
                     *     {@link org.apache.causeway.applib.services.email.EmailService}, at least).
                     * </p>
                     *
                     * @deprecated - now ignored, instead use <code>spring.mail.password</code>
                     */
                    @Deprecated
                    @DeprecatedConfigurationProperty(replacement = "spring.mail.password")
                    public String getPassword() {
                        return password;
                    }

                    /**
                     * Specifies the email address of the user sending the email.
                     *
                     * <p>
                     *     If the {@link #getUsername() username} is not specified, is also used as the username to
                     *     connect to the SMTP service.
                     * </p>
                     *
                     * <p>
                     *     This configuration property is mandatory (for the default implementation of the
                     *     {@link org.apache.causeway.applib.services.email.EmailService}, at least).
                     * </p>
                     */
                    @jakarta.validation.constraints.Email
                    private String address;
                }

                @Deprecated
                private final Tls tls = new Tls();
                @Deprecated
                @Data
                public static class Tls {
                    @Deprecated
                    private boolean enabled = true;

                    /**
                     * Whether TLS encryption should be started (that is, <code>STARTTLS</code>).
                     *
                     * @deprecated  - now ignored, instead use <code>spring.mail.javamail.properties.mail.smtp.starttls.enable</code>
                     */
                    @Deprecated
                    @DeprecatedConfigurationProperty(replacement = "spring.mail.javamail.properties.mail.smtp.starttls.enable")
                    public boolean isEnabled() {
                        return enabled;
                    }
                }
            }

            private final ApplicationFeatures applicationFeatures = new ApplicationFeatures();
            @Data
            public static class ApplicationFeatures {
                /**
                 * Whether the {@link org.apache.causeway.applib.services.appfeat.ApplicationFeatureRepository} (or the
                 * default implementation of that service, at least) should compute the set of
                 * <code>ApplicationFeature</code> that describe the metamodel
                 * {@link ApplicationFeaturesInitConfiguration#EAGERLY eagerly}, or lazily.
                 */
                ApplicationFeaturesInitConfiguration init = ApplicationFeaturesInitConfiguration.NOT_SPECIFIED;
            }

            private final RepositoryService repositoryService = new RepositoryService();
            @Data
            public static class RepositoryService {
                private boolean disableAutoFlush = false;

                /**
                 * Normally any queries are automatically preceded by flushing pending executions.
                 *
                 * <p>
                 * This key allows this behaviour to be disabled.
                 * </p>
                 *
                 * @deprecated - use instead <code>causeway.persistence.commons.repository-service.disable-auto-flush</code>
                 */
                @Deprecated
                @DeprecatedConfigurationProperty(replacement = "causeway.persistence.commons.repository-service.disable-auto-flush")
                public boolean isDisableAutoFlush() {
                    return disableAutoFlush;
                }
            }

            private final ExceptionRecognizer exceptionRecognizer = new ExceptionRecognizer();
            @Data
            public static class ExceptionRecognizer {

                private final Dae dae = new Dae();
                @Data
                public static class Dae {
                    /**
                     * Whether the {@link org.apache.causeway.applib.services.exceprecog.ExceptionRecognizer}
                     * implementation for Spring's DataAccessException - which attempts to sanitize any exceptions
                     * arising from object stores - should be disabled (meaning that exceptions will potentially
                     * propagate as more serious to the end user).
                     */
                    private boolean disable = false;
                }
            }

            private final Translation translation = new Translation();
            @Data
            public static class Translation {

                private final Po po = new Po();

                /**
                 * Specifies the relative resource path to look for translation files.
                 * <p>
                 * If {@code null} uses {@code servletContext.getResource("/WEB-INF/")}.
                 * <p>
                 * Replaces the former Servlet context parameter 'causeway.config.dir';
                 */
                private String resourceLocation = null;

                @Data
                public static class Po {
                    /**
                     * Specifies the initial mode for obtaining/discovering translations.
                     *
                     * <p>
                     *     There are three modes:
                     *     <ul>
                     *         <li>
                     *              <p>
                     *                  The default mode of {@link Mode#WRITE write} is appropriate for
                     *                  integration testing or prototyping, meaning that the service records any requests made of it
                     *                  but just returns the string unaltered.  This is a good way to discover new strings that
                     *                  require translation.
                     *              </p>
                     *         </li>
                     *         <li>
                     *              <p>
                     *                  The {@link Mode#READ read} mode is appropriate for production; the
                     *                  service looks up translations that have previously been captured.
                     *              </p>
                     *         </li>
                     *         <li>
                     *             <p>
                     *                 The {@link Mode#DISABLED disabled} performs no translation
                     *                 and simply returns the original string unchanged.  Unlike the write mode, it
                     *                 does <i>not</i> keep track of translation requests.
                     *             </p>
                     *         </li>
                     *     </ul>
                     * </p>
                     */
                    Mode mode = Mode.WRITE;
                }
            }

            private final EntityPropertyChangePublisher entityPropertyChangePublisher = new EntityPropertyChangePublisher();

            @Data
            public static class EntityPropertyChangePublisher {

                private final Bulk bulk = new Bulk();

                @Data
                public static class Bulk {

                    /**
                     * Determines the threshold as to whether to execute a set of entity changes in blk, in other words without a transaction flush in between.
                     *
                     * <p>
                     *     If the threshold is passed (by default, anything more than 1 entity to persist), then the {@link org.apache.causeway.applib.services.repository.RepositoryService#execInBulk(Callable)} API is used.
                     * </p>
                     */
                    int threshold = 1;
                }
            }
        }
    }

    private final Persistence persistence = new Persistence();
    @Data
    public static class Persistence {

        private final Commons commons = new Commons();
        @Data
        public static class Commons {

            private final RepositoryService repositoryService = new RepositoryService();
            @Data
            public static class RepositoryService {

                /**
                 * Normally any queries are automatically preceded by flushing pending executions.
                 *
                 * <p>
                 * This key allows this behaviour to be disabled.
                 *
                 * <p>
                 *     NOTE: this key is redundant for JPA/EclipseLink, which supports its own auto-flush using
                 *     <a href="https://www.eclipse.org/eclipselink/documentation/2.7/jpa/extensions/persistenceproperties_ref.htm#BABDHEEB">eclipselink.persistence-context.flush-mode</a>
                 * </p>
                 */
                private boolean disableAutoFlush = false;
            }

            private final EntityChangeTracker entityChangeTracker = new EntityChangeTracker();
            @Data
            public static class EntityChangeTracker {
                /**
                 * Normally any query submitted to {@link org.apache.causeway.applib.services.repository.RepositoryService#allMatches(Query)} will trigger a
                 * flush first, unless auto-flush has been {@link Core.RuntimeServices.RepositoryService#isDisableAutoFlush() disabled}.
                 *
                 * <p>
                 *     However, this auto-flush behaviour can be troublesome if the query occurs as a side-effect of the evaluation of a derived property,
                 *     whose value in turn is enlisted by an implementation of a subscriber (in particular {@link EntityPropertyChangeSubscriber}) which
                 *     captures the value of all properties (both persisted and derived).  However, this behaviour can (at least under JDO), result in a {@link java.util.ConcurrentModificationException}.
                 * </p>
                 *
                 * <p>
                 *     By default, {@link EntityChangeTracker} will therefore temporarily suppress any auto-flushing while this is ongoing.  The purpose
                 *     of this configuration property is to never suppress, ie always autoflush.
                 * </p>
                 */
                private boolean suppressAutoFlush = true;
                /**
                 * Provides a mechanism to globally enable or disable this service.
                 *
                 * <p>
                 *     By default this service is enabled (if added to the classpath as a module).
                 * </p>
                 */
                private boolean enabled = true;
            }
        }

        private final Schema schema = new Schema();
        @Data
        public static class Schema {

            /**
             * List of additional schemas to be auto-created.
             * <p>
             * Explicitly creates given list of schemas by using the specified
             * {@link #getCreateSchemaSqlTemplate()} to generate the actual SQL
             * statement against the configured data-source.
             * <p>
             * This configuration mechanism does not consider any schema-auto-creation
             * configuration (if any), that independently is provided the standard JPA way.
             */
            private final List<String> autoCreateSchemas = new ArrayList<>();

            /**
             * Does lookup additional "mapping-files" in META-INF/orm-<i>name</i>.xml
             * (equivalent to "mapping-file" entries in persistence.xml) and adds these
             * to those that are already configured the <i>Spring Data</i> way (if any).
             * @implNote not implemented for JDO
             */
            private final List<String> additionalOrmFiles = new ArrayList<>();

            /**
             * Vendor specific SQL syntax to create a DB schema.
             * <p>
             * This template is passed through {@link String#format(String, Object...)} to
             * make the actual SQL statement thats to be used against the configured data-source.
             * <p>
             * Default template is {@literal CREATE SCHEMA IF NOT EXISTS %S} with the schema name
             * converted to upper-case.
             * <p>
             * For MYSQL/MARIADB use escape like {@code `%S`}
             */
            private String createSchemaSqlTemplate = "CREATE SCHEMA IF NOT EXISTS %S";

        }
    }

    private final Prototyping prototyping = new Prototyping();
    @Data
    public static class Prototyping {

        private final H2Console h2Console = new H2Console();
        @Data
        public static class H2Console {
            /**
             * Whether to allow remote access to the H2 Web-Console,
             * which is a potential security risk when no web-admin password is set.
             * <p>
             * Corresponds to Spring Boot's "spring.h2.console.settings.web-allow-others" config property.
             */
            private boolean webAllowRemoteAccess = false;

            /**
             * Whether to generate a random password for access to the H2 Web-Console advanced features.
             *
             * <p>
             * If a password is generated, it is logged to the logging subsystem (Log4j2).
             * </p>
             *
             * <p>
             * Recommended (<code>true</code>) when {@link #isWebAllowRemoteAccess()} is also <code>true</code>.
             * </p>
             */
            private boolean generateRandomWebAdminPassword = true;
        }

        public enum IfHiddenPolicy {
            /**
             * The default  behaviour: any properties, collections or actions whose visibility has been vetoed
             * will not be shown in the UI.
             */
            HIDE,
            /**
             * To assist with the debugging security and similar: any properties, collections or actions whose
             * visibility has been vetoed will instead be shown as merely disabled.
             */
            SHOW_AS_DISABLED,
            /**
             * To assist with the debugging security and similar: any properties, collections or actions whose
             * visibility has been vetoed will instead be shown as merely disabled, and in addition the tooltips will
             * indicate the class name of the facet/advisor(s) that did the vetoing.
             */
            SHOW_AS_DISABLED_WITH_DIAGNOSTICS;
            public boolean isHide() { return this == HIDE; }
            public boolean isShowAsDisabled() { return this == SHOW_AS_DISABLED; }
            public boolean isShowAsDisabledWithDiagnostics() { return this == SHOW_AS_DISABLED_WITH_DIAGNOSTICS; }
        }

        /**
         * Whether and how to display any properties, actions and collections whose visibility has been vetoed.
         *
         * <p>
         *     By default, such object members are of course hidden.  However, this config property can be used to
         *     instead show these objects as disabled, with the tooltip indicating why the object member has been
         *     vetoed.  Setting the property to
         *     {@link IfHiddenPolicy#SHOW_AS_DISABLED_WITH_DIAGNOSTICS SHOW_AS_DISABLED_WITH_DIAGNOSTICS} shows
         *     additional detail in the tooltip.
         * </p>
         *
         * <p>
         *     This config property only applies in prototyping mode.
         * </p>
         */
        private IfHiddenPolicy ifHiddenPolicy = IfHiddenPolicy.HIDE;

        public enum IfDisabledPolicy {
            /**
             * The default  behaviour: any properties, collections or actions whose usability has been vetoed
             * will be shown as disabled in the UI.
             */
            DISABLE,
            /**
             * To assist with the debugging security and similar: any properties, collections or actions whose
             * usability has been vetoed will continue to be shown as disabled, and in addition the tooltips will
             * indicate the class name of the facet/advisor(s) that did the vetoing.
             */
            SHOW_AS_DISABLED_WITH_DIAGNOSTICS;
            public boolean isDisabled() { return this == DISABLE; }
            public boolean isShowAsDisabledWithDiagnostics() { return this == SHOW_AS_DISABLED_WITH_DIAGNOSTICS; }
        }

        /**
         * Whether and how to display any properties, actions and collections whose usability has been vetoed.
         *
         * <p>
         *     By default, such object members are shown as disabled, with the tooltip indicating why. Setting the
         *     property to {@link IfDisabledPolicy#SHOW_AS_DISABLED_WITH_DIAGNOSTICS SHOW_AS_DISABLED_WITH_DIAGNOSTICS} shows additional detail in the
         *     tooltip.
         * </p>
         *
         * <p>
         *     This config property only applies in prototyping mode.
         * </p>
         */
        private IfDisabledPolicy ifDisabledPolicy = IfDisabledPolicy.DISABLE;
    }

    private final Viewer viewer = new Viewer();
    @Data
    public static class Viewer {

        private final Common common = new Common();
        @Data
        public static class Common {

            private final Application application = new Application();
            @Data
            public static class Application {

                /**
                 * Label used on the about page.
                 */
                private String about;

                /**
                 * Either the location of the image file (relative to the class-path resource root),
                 * or an absolute URL.
                 *
                 * <p>
                 * This is rendered on the header panel. An image with a size of 160x40 works well.
                 * If not specified, the application.name is used instead.
                 * </p>
                 */
                @jakarta.validation.constraints.Pattern(regexp="^[^/].*$")
                private Optional<String> brandLogoHeader = Optional.empty();

                /**
                 * Either the location of the image file (relative to the class-path resource root),
                 * or an absolute URL.
                 *
                 * <p>
                 * This is rendered on the sign-in page. An image with a size of 400x40 works well.
                 * If not specified, the {@link Application#getName() application name} is used instead.
                 * </p>
                 */
                @jakarta.validation.constraints.Pattern(regexp="^[^/].*$")
                private Optional<String> brandLogoSignin = Optional.empty();

                /**
                 * Specifies the URL to use of the favIcon.
                 *
                 * <p>
                 *     This is expected to be a local resource.
                 * </p>
                 */
                @jakarta.validation.constraints.Pattern(regexp="^[^/].*$")
                private Optional<String> faviconUrl = Optional.empty();

                /**
                 * Specifies the file name containing the menubars.
                 *
                 * <p>
                 *     This is expected to be a local resource.
                 * </p>
                 */
                @NotNull @NotEmpty
                private String menubarsLayoutFile = "menubars.layout.xml";

                /**
                 * Identifies the application on the sign-in page
                 * (unless a {@link Application#brandLogoSignin sign-in} image is configured) and
                 * on top-left in the header
                 * (unless a {@link Application#brandLogoHeader header} image is configured).
                 */
                @NotNull @NotEmpty
                private String name = "Apache Causeway ™";

                /**
                 * The version of the application, eg 1.0, 1.1, etc.
                 *
                 * <p>
                 * If present, then this will be shown in the footer on every page as well as on the
                 * about page.
                 * </p>
                 */
                private String version;
            }

            /**
             * List of organisations or individuals to give credit to, shown as links and icons in the footer.
             * A maximum of 3 credits can be specified.
             *
             * <p>
             * IntelliJ unfortunately does not provide IDE completion for lists of classes; YMMV.
             * </p>
             *
             * <p>
             * @implNote - For further discussion, see for example
             * <a href="https://stackoverflow.com/questions/41417933/spring-configuration-properties-metadata-json-for-nested-list-of-objects">this stackoverflow question</a>
             * and <a href="https://github.com/spring-projects/spring-boot/wiki/IDE-binding-features#simple-pojo">this wiki page</a>.
             * </p>
             */
            private List<Credit> credit = new ArrayList<>();

            @Data
            public static class Credit {
                /**
                 * URL of an organisation or individual to give credit to, appearing as a link in the footer.
                 *
                 * <p>
                 *     For the credit to appear, the {@link #getUrl() url} must be provided along with either
                 *     {@link #getName() name} and/or {@link #getImage() image}.
                 * </p>
                 */
                @jakarta.validation.constraints.Pattern(regexp="^http[s]?://[^:]+?(:\\d+)?.*$")
                private String url;
                /**
                 * URL of an organisation or individual to give credit to, appearing as text in the footer.
                 *
                 * <p>
                 *     For the credit to appear, the {@link #getUrl() url} must be provided along with either
                 *     {@link #getName() name} and/or {@link #getImage() image}.
                 * </p>
                 */
                private String name;
                /**
                 * Name of an image resource of an organisation or individual, appearing as an icon in the footer.
                 *
                 * <p>
                 *     For the credit to appear, the {@link #getUrl() url} must be provided along with either
                 *     {@link #getName() name} and/or {@link #getImage() image}.
                 * </p>
                 */
                @jakarta.validation.constraints.Pattern(regexp="^[^/].*$")
                private String image;

                /**
                 * Whether enough information has been defined for the credit to be appear.
                 */
                public boolean isDefined() { return (name != null || image != null) && url != null; }
            }
        }

        private final Graphql graphql = new Graphql();
        @Data
        public static class Graphql {

            /**
             * Which style of schema to expose: &quot;simple&quot;, &quot;rich&quot; or some combination of both.
             *
             * @since 2.0 {@index}
             */
            public enum SchemaStyle {
                /**
                 * Expose only the &quot;simple&quot; schema, defining only fields that return the state of the domain
                 * objects but with no fields to represent additional facets of state (such as whether
                 * an action is hidden or disabled).
                 *
                 * <p>
                 *     Suitable for clients where the application logic and state is the responsibility of the client.
                 * </p>
                 */
                SIMPLE_ONLY,
                /**
                 * Expose only the &quot;rich&quot; schema, exposing not only fields that return the state of the domain
                 * objects but <i>also</i> with fields to represent additional facets of state (such as whether
                 * an action is hidden or disabled).
                 *
                 * <p>
                 *     Optionally, fields for Scenario (given/when/then) testing may also be added if the
                 *     {@link Schema.Rich#isEnableScenarioTesting()} config property is set.
                 * </p>
                 * <p>
                 *     Suitable for clients where the application logic and state remains in the backend, within the
                 *     domain model hosted by Causeway.
                 * </p>
                 */
                RICH_ONLY,
                /**
                 * Exposes both the simple and rich schemas, for the query have each under a field as defined by
                 * {@link Schema.Simple#getTopLevelFieldName()} (by default &quot;simple&quot;) and
                 * {@link Schema.Rich#getTopLevelFieldName()} (by default &quot;rich&quot;).
                 *
                 * <p>
                 *     For mutations, use the <i>simple</i> schema types.
                 * </p>
                 */
                SIMPLE_AND_RICH,
                /**
                 * Exposes both the simple and rich schemas, for the query have each under a field as defined by
                 * {@link Schema.Simple#getTopLevelFieldName()} (by default &quot;simple&quot;) and
                 * {@link Schema.Rich#getTopLevelFieldName()} (by default &quot;rich&quot;).
                 *
                 * <p>
                 *     For mutations, use the <i>rich</i> schema types.
                 * </p>
                 */
                RICH_AND_SIMPLE,
                ;

                public boolean isRich() {
                    return this == RICH_ONLY || this == SIMPLE_AND_RICH || this == RICH_AND_SIMPLE;
                }
                public boolean isSimple() {
                    return this == SIMPLE_ONLY || this == SIMPLE_AND_RICH || this == RICH_AND_SIMPLE;
                }
            }

            /**
             * Which {@link SchemaStyle} to expose, &quot;simple&quot; or &quot;rich&quot;.  By default both are
             * exposed under top-level field names.
             *
             * @see Schema.Rich#getTopLevelFieldName()
             * @see Schema.Simple#getTopLevelFieldName()
             */
            private SchemaStyle schemaStyle = SchemaStyle.RICH_AND_SIMPLE;

            private final Schema schema = new Schema();
            @Data
            public static class Schema {

                private final Simple simple = new Simple();
                @Data
                public static class Simple {

                    /**
                     * If the {@link #getSchemaStyle()} is set to {@link SchemaStyle#SIMPLE_AND_RICH}, defines the name of the
                     * top-level field under which the &quot;simple&quot; schema resides.
                     *
                     * <p>
                     *     Ignored for any other {@link #getSchemaStyle()}.
                     * </p>
                     */
                    private String topLevelFieldName = "simple";
                }

                private final Rich rich = new Rich();
                @Data
                public static class Rich {

                    /**
                     * If the {@link #getSchemaStyle()} is set to {@link SchemaStyle#SIMPLE_AND_RICH}, defines the name of the
                     * top-level field under which the &quot;rich&quot; schema resides.
                     *
                     * <p>
                     *     Ignored for any other {@link #getSchemaStyle()}.
                     * </p>
                     */
                    private String topLevelFieldName = "rich";

                    /**
                     * If the {@link #getSchemaStyle()} is set to either {@link SchemaStyle#RICH_ONLY} or
                     * {@link SchemaStyle#SIMPLE_AND_RICH}, then determines whether the &quot;Scenario&quot; field is included
                     * in order to allow given/when/then tests to be expressed.
                     *
                     * <p>
                     *     Ignored if the {@link #getSchemaStyle()} is {@link SchemaStyle#SIMPLE_ONLY}.
                     * </p>
                     */
                    private boolean enableScenarioTesting = false;
                }
            }

            public enum ApiVariant {
                /**
                 * Exposes only a Query API, of properties, collections and safe (query-only) actions.
                 * Any actions that mutate the state of the system (in other words are idempotent or non-idempotent)
                 * are excluded from the API, as is the ability to set properties.
                 */
                QUERY_ONLY,
                /**
                 * Exposes an API with Query for query/safe separate queries and field access, with mutating (idempotent
                 * and non-idempotent) actions and property setters instead surfaced as Mutations, as per the
                 * <a href="https://spec.graphql.org/June2018/#sec-Language.Operations">GraphQL spec</a>.
                 */
                QUERY_AND_MUTATIONS,
                /**
                 * Exposes an API with both Query and Mutations, but relaxes the constraints for the Query API by also
                 * including idempotent and non-idempotent actions and property setters.
                 *
                 * <p>
                 *     <b>IMPORTANT</b>: be aware that the resultant API is not compliant with the rules of the
                 *     GraphQL spec; in particular, it violates
                 *     <a href="https://spec.graphql.org/June2018/#sec-Language.Operations">2.3 Operations</a> which
                 *     states: &quot;query – [is] a read‐only fetch.&quot;
                 * </p>
                 */
                QUERY_WITH_MUTATIONS_NON_SPEC_COMPLIANT,
                ;
            }

            /**
             * Which variant of API to expose: {@link ApiVariant#QUERY_ONLY} (which suppresses any actions that mutate the state of the
             * system), or as {@link ApiVariant#QUERY_AND_MUTATIONS} (which additionally exposes actions that mutate the system as mutations)
             * or alternatively as {@link ApiVariant#QUERY_WITH_MUTATIONS_NON_SPEC_COMPLIANT}, a query-only schema that relaxes the read-only rule
             * by exposing actions that mutate the system; it is therefore not compliant with the GraphQL spec),
             */
            private ApiVariant apiVariant = ApiVariant.QUERY_AND_MUTATIONS;

            /**
             * Specifies which elements of the metamodel are included within the generated
             * GraphQL spec.
             *
             * @since 2.x {@index}
             */
            public enum ApiScope {

                /**
                 * The generated GraphQL spec is restricted only to include only
                 * {@link org.apache.causeway.applib.annotation.Nature#VIEW_MODEL view model}s.
                 *
                 * <p>
                 * Applicable when the GraphQL API is in use by third-party clients, ie public use and not
                 * under the control of the authors of the backend Apache Causeway application.
                 * Exposing entities also would couple the GraphQL client too deeply to the backend implementation.
                 * </p>
                 */
                VIEW_MODELS,
                /**
                 * The generated GraphQL spec is not restricted, includes both
                 * {@link org.apache.causeway.applib.annotation.Nature#ENTITY domain entities} as well as
                 * {@link org.apache.causeway.applib.annotation.Nature#VIEW_MODEL view model}s.
                 *
                 * <p>
                 * This is perfectly acceptable where the team developing the GraphQL client is the
                 * same as the team developing the backend service ... the use of the Web
                 * API between the client and server is a private implementation detail of
                 * the application.
                 * </p>
                 */
                ALL,
                ;
            }

            /**
             * Which domain objects to include the GraphQL schema.  By default, all domain objects are exposed
             * (entities and view models).
             */
            private ApiScope apiScope = ApiScope.ALL;

            private final MetaData metaData = new MetaData();
            @Data
            public static class MetaData {
                /**
                 * Note that field names <i>cannot</i> being with &quot;__&quot;, as that is reserved by the
                 * underlying GraphQL implementation.
                 */
                private String fieldName = "_meta";
            }

            private final Lookup lookup = new Lookup();
            @Data
            public static class Lookup {
                /**
                 * Lookup field prefix
                 */
                private String fieldNamePrefix = "";
                /**
                 * Lookup field suffix
                 */
                private String fieldNameSuffix = "";
                /**
                 * This is the name of the synthetic first argument used to locate the object to be looked up.
                 */
                private String argName = "object";
            }

            private final Mutation mutation = new Mutation();
            @Data
            public static class Mutation {
                /**
                 * The name of the synthetic argument of mutators representing the target domain object.
                 */
                private String targetArgName = "_target";
            }

            private final ScalarMarshaller scalarMarshaller = new ScalarMarshaller();
            @Data
            public static class ScalarMarshaller {

                /**
                 * For both JDK8's {@link java.time.LocalTime} and JodaTime's {@link org.joda.time.LocalTime}
                 */
                private String localTimeFormat = "HH:mm:ss";
                /**
                 * For both JDK8's {@link java.time.LocalDate} and JodaTime's {@link org.joda.time.LocalDate}
                 */
                private String localDateFormat = "yyyy-MM-dd";
                /**
                 * for JDK8's {@link java.time.ZonedDateTime} and JodaTime's {@link org.joda.time.DateTime}
                 */
                private String zonedDateTimeFormat = "yyyy-MM-dd'T'HH:mm:ssXXX";
            }

            /**
             * The different ways in which resources ({@link org.apache.causeway.applib.value.Blob} bytes,
             * {@link org.apache.causeway.applib.value.Clob} chars, grids and icons) can be downloaded from the
             * resource controller.
             */
            public enum ResponseType {
                /**
                 * Do not allow the resources to be downloaded at all.  This is the default.
                 *
                 * <p>
                 *     In this case any {@link org.apache.causeway.applib.value.Blob} and
                 *     {@link org.apache.causeway.applib.value.Clob} properties will <i>not</i> provide a link to
                 *     the URL.  Attempting to download from the resource controller will result in a 403 (forbidden).
                 * </p>
                 */
                FORBIDDEN,
                /**
                 * Allows resources to be downloaded directly.
                 *
                 * <p>
                 *     <b>IMPORTANT: </b> if enabling this configuration property, make sure that the <code>ResourcesController</code> endpoints
                 *     are secured appropriately.
                 * </p>
                 */
                DIRECT,
                /**
                 * Allows resources to be downloaded as attachments (using <code>Content-Disposition</code> header).
                 *
                 * <p>
                 *     <b>IMPORTANT: </b> if enabling this configuration property, make sure that the <code>ResourcesController</code> endpoints
                 *     are secured appropriately.
                 * </p>
                 */
                ATTACHMENT,
                ;
            }

            @Getter
            private final Resources resources = new Resources();
            @Data
            public static class Resources {
                /**
                 * How resources ({@link org.apache.causeway.applib.value.Blob} bytes,
                 * {@link org.apache.causeway.applib.value.Clob} chars, grids and icons) can be downloaded from the
                 * resource controller.
                 *
                 * <p>
                 *     By default the download of these resources if {@link ResponseType#FORBIDDEN}, but alternatively
                 *     they can be enabled to download either {@link ResponseType#DIRECT}ly or as an
                 *     {@link ResponseType#ATTACHMENT}.
                 * </p>
                 */
                private ResponseType responseType = ResponseType.FORBIDDEN;
            }

            @Getter
            private final Authentication authentication = new Authentication();
            @Data
            public static class Authentication {

                @Getter
                private final Fallback fallback = new Fallback();
                @Data
                public static class Fallback {

                    /**
                     * Used as the default username (if not provided by other means).
                     */
                    private String username;

                    /**
                     * Used as the set of roles for the default {@link #getUsername() username} (if not provided by other means).
                     */
                    private List<String> roles;
                }
            }
        }

        private final Restfulobjects restfulobjects = new Restfulobjects();
        @Data
        public static class Restfulobjects {

            @Getter
            private final Authentication authentication = new Authentication();
            @Data
            public static class Authentication {
                /**
                 * Defaults to <code>org.apache.causeway.viewer.restfulobjects.viewer.webmodule.auth.AuthenticationStrategyBasicAuth</code>.
                 */
                private String strategyClassName = "org.apache.causeway.viewer.restfulobjects.viewer.webmodule.auth.AuthenticationStrategyBasicAuth";
            }

            /**
             * Whether to enable the <code>x-ro-follow-links</code> support, to minimize round trips.
             *
             * <p>
             *     The RO viewer provides the capability for the client to set the optional
             *     <code>x-ro-follow-links</code> query parameter, as described in section 34.4 of the RO spec v1.0.
             *     If used, the resultant representation includes the result of following the associated link, but
             *     through a server-side "join", somewhat akin to GraphQL.
             * </p>
             *
             * <p>
             *     By default this functionality is disabled, this configuration property enables the feature.
             *     If enabled, then the representations returned are non-standard with respect to the RO Spec v1.0.
             * </p>
             */
            private boolean honorUiHints = false;

            /**
             * When rendering domain objects, if set the representation returned is stripped back to a minimal set,
             * excluding links to actions and collections and with a simplified representation of an object's
             * properties.
             *
             * <p>
             *     This is disabled by default.  If enabled, then the representations returned are non-standard with
             *     respect to the RO Spec v1.0.
             * </p>
             */
            private boolean objectPropertyValuesOnly = false;

            /**
             * If set, then any unrecognised <code>Accept</code> headers will result in an HTTP <i>Not Acceptable</i>
             * response code (406).
             */
            private boolean strictAcceptChecking = false;

            /**
             * If set, then the representations returned will omit any links to the formal domain-type representations.
             */
            private boolean suppressDescribedByLinks = false;

            /**
             * If set, then - should there be an interaction with an action, property or collection that is disabled -
             * then this will prevent the <code>disabledReason</code> reason from being added to the returned
             * representation.
             *
             * <p>
             *     This is disabled by default.  If enabled, then the representations returned are non-standard with
             *     respect to the RO Spec v1.0.
             * </p>
             */
            private boolean suppressMemberDisabledReason = false;

            /**
             * If set, then the <code>x-causeway-format</code> key (under <code>extensions</code>) for properties will be
             * suppressed.
             *
             * <p>
             *     This is disabled by default.  If enabled, then the representations returned are non-standard with
             *     respect to the RO Spec v1.0.
             * </p>
             */
            private boolean suppressMemberExtensions = false;

            /**
             * If set, then the <code>id</code> key for all members will be suppressed.
             *
             * <p>
             *     This is disabled by default.  If enabled, then the representations returned are non-standard with
             *     respect to the RO Spec v1.0.
             * </p>
             */
            private boolean suppressMemberId = false;

            /**
             * If set, then the detail link (in other words <code>links[rel='details' ...]</code>) for all members
             * will be suppressed.
             *
             * <p>
             *     This is disabled by default.  If enabled, then the representations returned are non-standard with
             *     respect to the RO Spec v1.0.
             * </p>
             */
            private boolean suppressMemberLinks = false;

            /**
             * If set, then the update link (in other words <code>links[rel='update'... ]</code> to perform a bulk
             * update of an object) will be suppressed.
             *
             * <p>
             *     This is disabled by default.  If enabled, then the representations returned are non-standard with
             *     respect to the RO Spec v1.0.
             * </p>
             */
            private boolean suppressUpdateLink = false;

            /**
             * If left unset (the default), then the RO viewer will use the {@link jakarta.ws.rs.core.UriInfo}
             * (injected using {@link jakarta.ws.rs.core.Context}) to figure out the base Uri (used to render
             * <code>href</code>s).
             *
             * <p>
             * This will be correct much of the time, but will almost certainly be wrong if there is a reverse proxy.
             * </p>
             *
             * <p>
             * If set, eg <code>https://dev.myapp.com/</code>, then this value will be used instead.
             * </p>
             */
            @jakarta.validation.constraints.Pattern(regexp="^http[s]?://[^:]+?(:\\d+)?/([^/]+/)*+$")
            private Optional<String> baseUri = Optional.empty();
        }

        private final Wicket wicket = new Wicket();
        @Data
        public static class Wicket {

            /**
             * Whether actions, that have explicit <code>hidden = Where</code> semantics
             * to enable them in tables,
             * should be gathered into an action column.
             * That is, collections of domain objects are presented in the UI as tables,
             * where corresponding domain object actions are gathered into an additional
             * (typically trailing) column (labeled 'action-column').
             */
            private boolean actionColumnEnabled = true;

            /**
             * Whether actions, that on click will show a dialog,
             * should be indicated by a trailing ellipsis on the action's label.
             * <p>
             * Applies to both, action buttons and action menu items.
             */
            private boolean actionIndicationWhenBoundToDialog = true;

            /**
             * Specifies the subclass of
             * <code>org.apache.causeway.viewer.wicket.viewer.wicketapp.CausewayWicketApplication</code> that is used to
             * bootstrap Wicket.
             *
             * <p>
             *     There is usually very little reason to change this from its default.
             * </p>
             */
            private String app = "org.apache.causeway.viewer.wicket.viewer.wicketapp.CausewayWicketApplication";

            /**
             * Whether the Ajax debug should be shown, by default this is disabled.
             */
            private boolean ajaxDebugMode = false;

            /**
             * The base path at which the Wicket viewer is mounted.
             */
            @jakarta.validation.constraints.Pattern(regexp="^[/](.*[/]|)$") @NotNull @NotEmpty
            private String basePath = "/wicket/";

            /**
             * If the end user uses a deep link to access the Wicket viewer, but is not authenticated, then this
             * configuration property determines whether to continue through to that original destination once
             * authenticated, or simply to go to the home page.
             *
             * <p>
             *     The default behaviour is to honour the original destination requested.
             * </p>
             */
            private boolean clearOriginalDestination = false;

            /**
             * Whether the clear-field-button, that allows to clear a null-able (optional) field
             * (a property or a dialog argument) is enabled for rendering or not.
             *
             * <p>
             *     The default is to enable (show) the clear-field-button.
             * </p>
             */
            private boolean clearFieldButtonEnabled = true;

            /**
             * In prototyping mode, a text icon is appended to any property that is disabled, with its tool-tip explaining why the property is disabled.
             * This configuration property can be used to suppress the icon, even in prototyping mode, if desired.
             *
             * <p>
             *     The default is to enable (show) the text icon (if in prototyping mode).
             * </p>
             */
            private boolean disableReasonExplanationInPrototypingModeEnabled = true;

            /**
             * URL of file to read any custom CSS, relative to <code>static</code> package on the class path.
             *
             * <p>
             *     A typical value is <code>css/application.css</code>.  This will result in this file being read
             *     from the <code>static/css</code> directory (because static resources such as CSS are mounted by
             *     Spring by default under <code>static</code> package).
             * </p>
             */
            @jakarta.validation.constraints.Pattern(regexp="^[^/].*$")
            private Optional<String> css = Optional.empty();

            /**
             * Whether the dialog mode rendered when invoking actions on domain objects should be to use
             * the sidebar (the default) or to use a modal dialog.
             *
             * <p>
             *     This can be overridden on a case-by-case basis using {@link ActionLayout#promptStyle()}.
             * </p>
             */
            private DialogMode dialogMode = DialogMode.SIDEBAR;

            /**
             * Whether the dialog mode rendered when invoking actions on domain services (that is, menus) should be to
             * use a modal dialog (the default) or to use the sidebar panel.
             *
             * <p>
             *     This can be overridden on a case-by-case basis using {@link ActionLayout#promptStyle()}.
             * </p>
             */
            private DialogMode dialogModeForMenu = DialogMode.MODAL;

            /**
             * URL of file to read any custom JavaScript, relative to <code>static</code> package on the class path.
             *
             * <p>
             *     A typical value is <code>js/application.js</code>.  This will result in this file being read
             *     from the <code>static/js</code> directory (because static resources such as CSS are mounted by
             *     Spring by default under <code>static</code> package).
             * </p>
             */
            @jakarta.validation.constraints.Pattern(regexp="^[^/].*$")
            private Optional<String> js = Optional.empty();

            /**
             * If specified, then is rendered on each page to enable live reload.
             *
             * <p>
             *     Configuring live reload also requires an appropriate plugin to the web browser (eg see
             *     <a href="http://livereload.com/">livereload.com</a> and a mechanism to trigger changes, eg by
             *     watching <code>Xxx.layout.xml</code> files.
             * </p>
             */
            private Optional<String> liveReloadUrl = Optional.empty();

            /**
             * The maximum number of characters to use to render the title of a domain object (alongside the icon)
             * in any table, if not otherwise overridden by either {@link #getMaxTitleLengthInParentedTables()}
             * or {@link #getMaxTitleLengthInStandaloneTables()}.
             *
             * <p>
             *     If truncated, then the remainder of the title will be replaced with ellipses (...).
             * </p>
             */
            private int maxTitleLengthInTables = 12;

            private static boolean isMaxTitleLenghtValid(final int len) {
                return len>=0;
            }
            private int asTitleLenght(final int len) {
                return isMaxTitleLenghtValid(len)
                        ? len
                        : getMaxTitleLengthInTables();
            }

            private int maxTitleLengthInParentedTables = -1;

            /**
             * The maximum number of characters to use to render the title of a domain object (alongside the icon) in a
             * parented table.
             *
             * <p>
             *     If truncated, then the remainder of the title will be replaced with ellipses (...).
             * </p>
             *
             * <p>
             *     If invalid or not specified, then the value of {@link #getMaxTitleLengthInTables()} is used.
             * </p>
             */
            public int getMaxTitleLengthInParentedTables() {
                return asTitleLenght(maxTitleLengthInParentedTables);
            }

            public void setMaxTitleLengthInParentedTables(final int val) {
                maxTitleLengthInParentedTables = val;
            }

            private int maxTitleLengthInStandaloneTables = -1;

            /**
             * The maximum number of characters to use to render the title of a domain object (alongside the icon)
             * in a standalone table, ie the result of invoking an action.
             *
             * <p>
             *     If truncated, then the remainder of the title will be replaced with ellipses (...).
             * </p>
             *
             * <p>
             *     If invalid or not specified, then the value of {@link #getMaxTitleLengthInTables()} is used.
             * </p>
             */
            public int getMaxTitleLengthInStandaloneTables() {
                return asTitleLenght(maxTitleLengthInStandaloneTables);
            }
            /**
             * The maximum length that a title of an object will be shown when rendered in a standalone table;
             * will be truncated beyond this (with ellipses to indicate the truncation).
             * <p>
             * If set to negative or zero, sets defaults instead.
             */
            public void setMaxTitleLengthInStandaloneTables(final int val) {
                maxTitleLengthInStandaloneTables = val;
            }

            /**
             * If a table has no property columns,
             * for the title column this value is used,
             * to determine how many characters to render for the table element titles.
             * <p>
             * Introduced for the case when max-title-length is set to zero for tables in general,
             * that if a table has no property columns an exception to that title suppression can be made.
             */
            private int maxTitleLengthInTablesNotHavingAnyPropertyColumn = 80;

            /**
             * Whether to use a modal dialog for property edits and for actions associated with properties.
             *
             * <p>
             * This can be overridden on a case-by-case basis using <code>@PropertyLayout#promptStyle</code> and
             * <code>@ActionLayout#promptStyle</code>.
             * </p>
             *
             * <p>
             * This behaviour is disabled by default; the viewer will use an inline prompt in these cases, making for a smoother
             * user experience.
             * </p>
             */
            private PromptStyle promptStyle = PromptStyle.INLINE;

            /**
             * Whether to redirect to a new page, even if the object being shown (after an action invocation or a property edit)
             * is the same as the previous page.
             *
             * <p>
             * This behaviour is disabled by default; the viewer will update the existing page if it can, making for a
             * smoother user experience. If enabled then this reinstates the pre-1.15.0 behaviour of redirecting in all cases.
             * </p>
             */
            private boolean redirectEvenIfSameObject = false;

            /**
             * In Firefox and more recent versions of Chrome 54+, cannot copy out of disabled fields; instead we use the
             * readonly attribute (https://www.w3.org/TR/2014/REC-html5-20141028/forms.html#the-readonly-attribute)
             *
             * <p>
             * This behaviour is enabled by default but can be disabled using this flag
             * </p>
             */
            private boolean replaceDisabledTagWithReadonlyTag = true;

            /**
             * Whether to disable a form submit button after it has been clicked, to prevent users causing an error if they
             * do a double click.
             *
             * This behaviour is enabled by default, but can be disabled using this flag.
             */
            private boolean preventDoubleClickForFormSubmit = true;

            /**
             * Whether to disable a no-arg action button after it has been clicked, to prevent users causing an error if they
             * do a double click.
             *
             * <p>
             * This behaviour is enabled by default, but can be disabled using this flag.
             * </p>
             */
            private boolean preventDoubleClickForNoArgAction = true;

            /**
             * Whether to show the footer menu bar.
             *
             * <p>
             *     This is enabled by default.
             * </p>
             */
            private boolean showFooter = true;

            /**
             * Whether Wicket tags should be stripped from the markup.
             *
             * <p>
             *      By default this is enabled, in other words Wicket tags are stripped.  Please be aware that if
             *      tags are <i>not</i> stripped, then this may break CSS rules on some browsers.
             * </p>
             */
            private boolean stripWicketTags = true;

            /**
             * Whether to suppress the sign-up link on the sign-in page.
             *
             * <p>
             *     Although this is disabled by default (in other words the sign-up link is not suppressed), note that
             *     in addition the application must provide an implementation of the
             *     {@link UserRegistrationService} as well as a
             *     configured {@link EmailNotificationService} (same conditions
             *     as for the {@link #isSuppressPasswordReset()} password reset link).
             * </p>
             */
            private boolean suppressSignUp = false;

            /**
             * Whether to suppress the password reset link on the sign-in page.
             *
             * <p>
             *     Although this is disabled by default (in other words the 'reset password' link is not suppressed),
             *     note that in addition the application must provide an implementation of the
             *     {@link UserRegistrationService} as well as a
             *     configured {@link EmailNotificationService} (same conditions
             *     as for the {@link #isSuppressSignUp()} sign-up link).
             * </p>
             */
            private boolean suppressPasswordReset = false;

            /**
             * How to interpret tooltip content, e.g. as HTML or TEXT. default = TEXT
             */
            private TextMode tooltipTextMode = TextMode.defaults();

            /**
             * Whether to show an indicator for a form submit button that it has been clicked.
             *
             * <p>
             * This behaviour is enabled by default.
             * </p>
             */
            private boolean useIndicatorForFormSubmit = true;

            /**
             * Whether to show an indicator for a no-arg action button that it has been clicked.
             *
             * <p>
             * This behaviour is enabled by default.
             * </p>
             */
            private boolean useIndicatorForNoArgAction = true;

            /**
             * Whether to show an indicator for a sortable column (that is an up-down arrow icon).
             *
             * <p>
             * This behaviour is disabled by default.
             * </p>
             */
            private boolean useIndicatorForSortableColumn = false;

            /**
             * Whether the Wicket source plugin should be enabled; if so, the markup includes links to the Wicket source.
             *
             * <p>
             *     This behaviour is disabled by default.  Please be aware that enabling it can substantially impact
             *     performance.
             * </p>
             */
            private boolean wicketSourcePlugin = false;

            private final BookmarkedPages bookmarkedPages = new BookmarkedPages();
            @Data
            public static class BookmarkedPages {

                /**
                 * Whether the panel providing linsk to previously visited object should be accessible from the top-left of the header.
                 */
                private boolean showChooser = true;

                /**
                 * Specifies the maximum number of bookmarks to show.
                 *
                 * <p>
                 *     These are aged out on an MRU-LRU basis.
                 * </p>
                 */
                private int maxSize = 15;

                /**
                 * Whether the drop-down list of previously visited objects should be shown in the footer.
                 */
                private boolean showDropDownOnFooter = true;

            }

            private final Breadcrumbs breadcrumbs = new Breadcrumbs();
            @Data
            public static class Breadcrumbs {
                /**
                 * Whether to enable the 'where am i' feature, in other words the breadcrumbs.
                 */
                private boolean enabled = true;
                /**
                 *
                 */
                private int maxParentChainLength = 64;
            }

            private final DatePicker datePicker = new DatePicker();
            @Data
            public static class DatePicker {

                /**
                 * Defines the first date available in the date picker.
                 * <p>
                 * As per http://eonasdan.github.io/bootstrap-datetimepicker/Options/#maxdate, in ISO format (per https://github.com/moment/moment/issues/1407).
                 * <p>
                 * Use time zone 'Z', as the date/time picker UI component is not wired up to support time-zones.
                 */
                @NotEmpty @NotNull
                private String minDate = "1900-01-01T00:00:00.000Z";

                /**
                 * Defines the first date available in the date picker.
                 * <p>
                 * As per http://eonasdan.github.io/bootstrap-datetimepicker/Options/#maxdate, in ISO format (per https://github.com/moment/moment/issues/1407).
                 * <p>
                 * Use time zone 'Z', as the date/time picker UI component is not wired up to support time-zones.
                 */
                @NotEmpty @NotNull
                private String maxDate = "2100-01-01T00:00:00.000Z";

//XXX probably needed by TempusDominus 5+
//                public final java.util.Date minDateAsJavaUtilDate() {
//                    return asJavaUtilDate(getMinDate());
//                }
//
//                public final java.util.Date maxDateAsJavaUtilDate() {
//                    return asJavaUtilDate(getMaxDate());
//                }
//
//                private static java.util.Date asJavaUtilDate(final String input) {
//                    return new Date(
//                            DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(input, OffsetDateTime::from)
//                            .toEpochSecond());
//                }

                /**
                 * Whether the date picker should automatically be shown when the field gains focus.
                 *
                 * <p>
                 *     The default is to show the picker when the user clicks into the field.
                 * </p>
                 *
                 * <p>
                 *     Corresponds to the <code>allowInputToggle</code> config property of the underlying library,
                 *     (TempusDominus).
                 * </p>
                 */
                private boolean popupOnFocus = false;

            }

            private final DevelopmentUtilities developmentUtilities = new DevelopmentUtilities();
            @Data
            public static class DevelopmentUtilities {

                /**
                 * Determines whether debug bar and other stuff influenced by
                 * <code>DebugSettings#isDevelopmentUtilitiesEnabled()</code> is enabled or not.
                 *
                 * <p>
                 *     By default, depends on the mode (prototyping = enabled, server = disabled).  This property acts as an override.
                 * </p>
                 */
                private boolean enable = false;
            }

            private final RememberMe rememberMe = new RememberMe();
            @Data
            public static class RememberMe {
                /**
                 * Whether the sign-in page should have a &quot;remember me&quot; link (the default), or if it should
                 * be suppressed.
                 *
                 * <p>
                 *     If &quot;remember me&quot; is available and checked, then the viewer will allow users to login
                 *     based on encrypted credentials stored in a cookie.  An {@link #getEncryptionKey() encryption key}
                 *     can optionally be specified.
                 * </p>
                 */
                private boolean suppress = false;

                /**
                 * If the &quot;remember me&quot; feature is available, specifies the key to hold the encrypted
                 * credentials in the cookie.
                 */
                private String cookieKey = "causewayWicketRememberMe";
                /**
                 * If the &quot;remember me&quot; feature is available, optionally specifies an encryption key
                 * (a complex string acting as salt to the encryption algorithm) for computing the encrypted
                 * credentials.
                 *
                 * <p>
                 *     If not set, then (in production mode) the Wicket viewer will compute a random key each time it
                 *     is started.  This will mean that any credentials stored between sessions will become invalid.
                 * </p>
                 *
                 * <p>
                 *     Conversely, if set then (in production mode) then the same salt will be used each time the app
                 *     is started, meaning that cached credentials can continue to be used across restarts.
                 * </p>
                 *
                 * <p>
                 *     In prototype mode this setting is effectively ignored, because the same key will always be
                 *     provided (either as set, or a fixed literal otherwise).
                 * </p>
                 */
                private Optional<String> encryptionKey = Optional.empty();
            }

            private final Themes themes = new Themes();
            @Data
            public static class Themes {

                /**
                 * A comma separated list of enabled theme names, as defined by https://bootswatch.com.
                 */
                private List<String> enabled = listOf("Cosmo","Flatly","Darkly","Sandstone","United");

                /**
                 * The initial theme to use.
                 *
                 * <p>
                 *     Expected to be in the list of {@link #getEnabled()} themes.
                 * </p>
                 */
                @NotEmpty @NotNull
                private String initial = "Flatly";

                /**
                 * Whether the theme chooser widget should be available in the footer.
                 */
                private boolean showChooser = false;
            }

            private final Welcome welcome = new Welcome();
            @Data
            public static class Welcome {

                /**
                 * Text to be displayed on the application’s home page, used as a fallback if
                 * welcome.file is not specified. If a @HomePage action exists, then that will take
                 * precedence.
                 */
                private String text;
            }

            private final MessagePopups messagePopups = new MessagePopups();
            @Data
            public static class MessagePopups {

                /**
                 * How long the info popup should display before disappearing.
                 *
                 * <p>
                 *     A value of 0 means do not disappear automatically.
                 * </p>
                 */
                Duration infoDelay = Duration.ofMillis(3500);

                /**
                 * How long the warning popup should display before disappearing.
                 *
                 * <p>
                 *     A value of 0 (the default) means do not disappear automatically.
                 * </p>
                 */
                Duration warningDelay = Duration.ofMillis(0);

                /**
                 * How long the error popup should display before disappearing.
                 *
                 * <p>
                 *     A value of 0 (the default) means do not disappear automatically.
                 * </p>
                 */
                Duration errorDelay = Duration.ofMillis(0);

                /**
                 * How far in from the edge the popup should display
                 */
                int offset = 100;

                private final Placement placement = new Placement();
                @Data
                public static class Placement {

                    public static enum Vertical {
                        TOP, BOTTOM
                    }

                    public static enum Horizontal {
                        LEFT, RIGHT
                    }

                    /**
                     * Whether to display popups at the top or the bottom of the page.
                     *
                     * <p>
                     * The default is to show them at the top.
                     * </p>
                     */
                    Vertical vertical = Vertical.TOP;

                    /**
                     * Whether to display popups aligned ot the left or right of the page.
                     *
                     * <p>
                     * The default is to show them aligned to the right
                     * </p>
                     */
                    Horizontal horizontal = Horizontal.RIGHT;
                }
            }
        }
    }

    private final ValueTypes valueTypes = new ValueTypes();
    @Data
    public static class ValueTypes {

        private final Temporal temporal = new Temporal();
        @Data
        public static class Temporal {
            private final TemporalEditingPattern editing = new TemporalEditingPattern();
            private final TemporalDisplayPattern display = new TemporalDisplayPattern();
        }

        private final BigDecimal bigDecimal = new BigDecimal();
        @Data
        public static class BigDecimal {

            /**
             * Indicates how to derive the min fractional facet (the minimum number of digits after the decimal point).
             *
             * <p>
             * If this flag is set, then the {@link Digits#fraction()} annotation or ORM equivalent (the JDO
             * <code>@Column#scale</code> or the JPA {@link Column#scale()}) should be used for the
             * <code>MinFractionalFacet</code> as well as the <code>MaxFractionalFacet</code>.
             * </p>
             *
             * <p>
             * What this means in practice is that a numeric values will be rendered to the same number of fractional
             * digits, irrespective of whether they are whole numbers or fractional.  For example, with a scale of 2,
             * then &quot;123.4532&quot; will be rendered as &quot;123.45&quot;, while &quot;123&quot; will be rendered
             * as &quot;123.00&quot;.
             * </p>
             *
             * <p>
             * If this flag is NOT set, or if it is set but there is no annotation, then the {@link #minScale} config
             * property is used as a fallback.
             * </p>
             *
             * <p>
             * If there is no fallback, then it means that a big decimal such as &quot;123.00&quot; will be presented as
             * just &quot;123&quot; (that is, the shortest possible textual representation).
             * </p>
             */
            private boolean useScaleForMinFractionalFacet = true;

            /**
             * The minimum scale to use for all {@link java.math.BigDecimal}s.
             *
             * <p>
             * Is only used if the minimum scale has not been specified explicitly by some other means, typically
             * either {@link Digits#fraction()} or an ORM semantic such as the (JPA) {@link Column#scale()}.
             *
             * @deprecated - use {@link Display#getMinScale()} instead
             */
            @Deprecated(since = "2.2.0")
            private Integer minScale = null;

            @DeprecatedConfigurationProperty(replacement = "causeway.value-types.big-decimal.display.min-scale", reason = "Moved")
            public Integer getMinScale() {
                return minScale;
            }

            private final Editing editing = new Editing();
            @Data
            public static class Editing {
                /**
                 * A common use of {@link java.math.BigDecimal} is as a money value.  In some locales (eg English), the
                 * &quot;,&quot; (comma) is the grouping (thousands) separator wihle the &quot;.&quot; (period) acts as a
                 * decimal point, but in others (eg France, Italy) it is the other way around.
                 *
                 * <p>
                 *     Surprisingly perhaps, a string such as "123,99", when parsed ((by {@link java.text.DecimalFormat})
                 *     in an English locale, is not rejected but instead is evaluated as the value 12399L.  That's almost
                 *     certainly not what the end-user would have expected, and results in a money value 100x too large.
                 * </p>
                 *
                 * <p>
                 *     The purpose of this configuration property is to remove the confusion by simply disallowing the
                 *     thousands separator from being part of the input string.
                 * </p>
                 *
                 * <p>
                 *     For maximum safety, allowing the grouping separator is disallowed, but the alternate (original)
                 *     behaviour can be reinstated by setting this config property back to <code>true</code>.
                 * </p>
                 *
                 * <p>
                 *     The same configuration property is also used for rendering the value.
                 * </p>
                 *
                 * @see Display#isUseGroupingSeparator()
                 */
                private boolean useGroupingSeparator = false;
                /**
                 * When a BigDecimal is presented for editing, whether it should enforce the scale, possibly meaning
                 * trailing '0's to pad).  This is probably appropriate for BigDecimals that represent a money amount.
                 */
                private boolean preserveScale = false;
            }

            private final Display display = new Display();
            @Data
            public static class Display {

                /**
                 * The minimum scale to use for all {@link java.math.BigDecimal}s.
                 *
                 * <p>
                 * Is only used if the minimum scale has not been specified explicitly by some other means, typically
                 * either {@link Digits#fraction()} or an ORM semantic such as the (JPA) {@link Column#scale()}.
                 */
                private Integer minScale = null;

                /**
                 * Whether to use a grouping (thousands) separator (eg the &quot;,&quot; (comma) in the English locale)
                 * when rendering a big decimal.
                 *
                 * @see Editing#isUseGroupingSeparator()
                 */
                private boolean useGroupingSeparator = true;
            }

            /**
             * A common use of {@link java.math.BigDecimal} is as a money value.  In some locales (eg English), the
             * &quot;,&quot; (comma) is the grouping (thousands) separator wihle the &quot;.&quot; (period) acts as a
             * decimal point, but in others (eg France, Italy) it is the other way around.
             *
             * <p>
             *     Surprisingly perhaps, a string such as "123,99", when parsed ((by {@link java.text.DecimalFormat})
             *     in an English locale, is not rejected but instead is evaluated as the value 12399L.  That's almost
             *     certainly not what the end-user would have expected, and results in a money value 100x too large.
             * </p>
             *
             * <p>
             *     The purpose of this configuration property is to remove the confusion by simply disallowing the
             *     thousands separator from being part of the input string.
             * </p>
             *
             * <p>
             *     For maximum safety, allowing the grouping separator is disallowed, but the alternate (original)
             *     behaviour can be reinstated by setting this config property back to <code>true</code>.
             * </p>
             *
             * <p>
             *     The same configuration property is also used for rendering the value.
             * </p>
             *
             * @deprecated - use {@link Editing#isUseGroupingSeparator()} instead.
             */
            @Deprecated(since = "2.2.0")
            private boolean useGroupingSeparator = false;

            @DeprecatedConfigurationProperty(replacement = "causeway.value-types.big-decimal.editing.use-grouping-separator")
            public boolean isUseGroupingSeparator() {
                return useGroupingSeparator;
            }
        }

        private final Kroki kroki = new Kroki();
        @Data
        public static class Kroki {
            /**
             * If set, adds plantuml support to the AsciiDoc value type.
             * <p>
             * Eg. via docker instance like {@literal yuzutech/kroki}.
             */
            private URL backendUrl = null;

            /**
             * Max time for requests to the {@link #getBackendUrl()},
             * when waiting for a response. (default: 5 seconds)
             */
            private Duration requestTimeout = Duration.ofMillis(5000);
        }

    }

    private final Testing testing = new Testing();
    @Data
    public static class Testing {
        private final Fixtures fixtures = new Fixtures();
        @Data
        public static class Fixtures {
            /**
             * Indicates the fixture script class to run initially.
             *
             * <p>
             *     Intended for use when prototyping against an in-memory database (but will run in production mode
             *     as well if required).
             * </p>
             */
            @AssignableFrom("org.apache.causeway.testing.fixtures.applib.fixturescripts.FixtureScript")
            private Class<?> initialScript = null;

            private final FixtureScriptsSpecification fixtureScriptsSpecification = new FixtureScriptsSpecification();
            @Data
            public static class FixtureScriptsSpecification {
                /**
                 * Specifies the base package from which to search for fixture scripts.
                 *
                 * <p>
                 *     Either this or {@link #getPackagePrefix() packagePrefix} must be specified.  This property is
                 *     used by preference.
                 * </p>
                 *
                 * @see #getPackagePrefix()
                 */
                private Class<?> contextClass = null;
                /**
                 * Specifies the base package from which to search for fixture scripts.
                 *
                 * <p>
                 *     Either this or {@link #getContextClass()} must be specified; {@link #getContextClass()} is
                 *     used by preference.
                 * </p>
                 *
                 * @see #getContextClass()
                 */
                private String packagePrefix = null;

                /**
                 * How to handle objects that are to be added into a <code>FixtureResult</code> but which are not yet
                 * persisted.
                 */
                public enum NonPersistedObjectsStrategy {
                    PERSIST,
                    IGNORE
                }

                /**
                 * How to handle fixture scripts that are submitted to be executed more than once.
                 *
                 * <p>
                 *     Note that this is a global setting of the <code>FixtureScripts</code> service; there isn't
                 *     (currently) any way to mix-and-match fixture scripts that are written with differing semantics
                 *     in mind.  Ideally it should be the responsibility of the fixture script itself to determine
                 *     whether it should be run.
                 * </p>
                 */
                public enum MultipleExecutionStrategy {
                    /**
                     * Any given fixture script (or more precisely, any fixture script instance for a particular fixture script
                     * class) can only be run once.
                     *
                     * <p>
                     *     This strategy represents the original design of fixture scripts service.  Specifically, it allows an
                     *     arbitrary graph of fixture scripts (eg A -> B -> C, A -> B -> D, A -> C -> D) to be created each
                     *     specifying its dependencies, and without having to worry or co-ordinate whether those prerequisite
                     *     fixture scripts have already been run.
                     * </p>
                     * <p>
                     *     The most obvious example is a global teardown script; every fixture script can require this to be
                     *     called, but it will only be run once.
                     * </p>
                     * <p>
                     *     Note that this strategy treats fixture scripts as combining both the 'how' (which business action(s) to
                     *     call) and the also the 'what' (what the arguments are to those actions).
                     * </p>
                     */
                    EXECUTE_ONCE_BY_CLASS,
                    /**
                     * Any given fixture script can only be run once, where the check to determine if a fixture script has already
                     * been run is performed using value semantics.
                     *
                     * <p>
                     *     This strategy is a half-way house between the {@link #EXECUTE_ONCE_BY_VALUE} and {@link #EXECUTE}
                     *     strategies, where we want to prevent a fixture from running more than once, where by "fixture" we mean
                     *     the 'what' - the data to be loaded up; the 'how' is unimportant.
                     * </p>
                     *
                     * <p>
                     *     This strategy was introduced in order to better support the <tt>ExcelFixture</tt> fixture script
                     *     (provided by the (non-ASF) Causeway Addons'
                     *     <a href="https://github.com/causewayaddons/causeway-module-excel">Excel module</a>.  The <tt>ExcelFixture</tt>
                     *     takes an Excel spreadsheet as the 'what' and loads up each row.  So the 'how' is re-usable (therefore
                     *     the {@link #EXECUTE_ONCE_BY_CLASS} doesn't apply) on the other hand we don't want the 'what' to be
                     *     loaded more than once (so the {@link #EXECUTE} strategy doesn't apply either).  The solution is for
                     *     <tt>ExcelFixture</tt> to have value semantics (a digest of the spreadsheet argument).
                     * </p>
                     */
                    EXECUTE_ONCE_BY_VALUE,
                    /**
                     * Allow fixture scripts to run as requested.
                     *
                     * <p>
                     *     This strategy is conceptually the simplest; all fixtures are run as requested.  However, it is then
                     *     the responsibility of the programmer to ensure that fixtures do not interfere with each other.  For
                     *     example, if fixture A calls fixture B which calls teardown, and fixture A also calls fixture C that
                     *     itself calls teardown, then fixture B's setup will get removed.
                     * </p>
                     * <p>
                     *     The workaround to the teardown issue is of course to call the teardown fixture only once in the test
                     *     itself; however even then this strategy cannot cope with arbitrary graphs of fixtures.  The solution
                     *     is for the fixture list to be flat, one level high.
                     * </p>
                     */
                    EXECUTE;
                }

                /**
                 * Indicates whether, if a fixture script (or more precisely any other fixture scripts of the same
                 * class) is encountered more than once in a graph of dependencies, it should be executed again or
                 * skipped.
                 *
                 * <p>
                 *     The default is to fixture scripts are executed only once per class.
                 * </p>
                 *
                 * <p>
                 * Note that this policy can be overridden on a fixture-by-fixture basis if the fixture implements
                 * <code>FixtureScriptWithExecutionStrategy</code>.
                 * </p>
                 */
                private MultipleExecutionStrategy multipleExecutionStrategy = MultipleExecutionStrategy.EXECUTE_ONCE_BY_CLASS;

                /**
                 * Indicates whether objects that are returned as a fixture result should be automatically persisted
                 * if required (the default) or not.
                 */
                private NonPersistedObjectsStrategy nonPersistedObjectsStrategy = NonPersistedObjectsStrategy.PERSIST;

                @AssignableFrom("org.apache.causeway.testing.fixtures.applib.fixturescripts.FixtureScript")
                private Class<?> recreate = null;

                @AssignableFrom("org.apache.causeway.testing.fixtures.applib.fixturescripts.FixtureScript")
                private Class<?> runScriptDefault = null;

            }

        }
    }

    private final Extensions extensions = new Extensions();
    @Valid
    @Data
    public static class Extensions {

        private final AuditTrail auditTrail = new AuditTrail();
        @Data
        public static class AuditTrail {

            /**
             * As per {@link AuditTrail#getPersist()}.
             *
             * <p>
             *     Implementation note: we use an enum here (rather than a simple boolean) to allow for future
             *     enhancements.
             * </p>
             */
            public enum PersistPolicy {
                /**
                 * Persist to the audit trail.  This is the default.
                 */
                ENABLED,
                /**
                 * Do <i>NOT</i> persist to the audit trail.
                 */
                DISABLED;

                public boolean isEnabled() { return this == ENABLED; }
                public boolean isDisabled() { return this == DISABLED; }
            }

            /**
             * Whether the {@link EntityPropertyChangeSubscriber} implementation provided by this extension (which
             * persists property changes to the audit trail) is enabled or not.
             *
             * <p>
             *     One reason to use this option is if you wish to provide your own implementation that wraps
             *     or delegates to the default implementation of {@link EntityPropertyChangeSubscriber} that is
             *     provided by the <i>audittrail</i> extension.  Because entity property changes are published to
             *     <i>all</i> subscribers on the class path, you can disable the default implementation from
             *     doing anything using this setting.
             * </p>
             */
            private PersistPolicy persist = PersistPolicy.ENABLED;
        }

        private final CommandLog commandLog = new CommandLog();
        @Data
        public static class CommandLog {

            /**
             * As per {@link CommandLog#getPersist()}.
             *
             * <p>
             *     Implementation note: we use an enum here (rather than a simple boolean) to allow for future
             *     enhancements.
             * </p>
             */
            public enum PersistPolicy {
                /**
                 * Persist to the command log .  This is the default.
                 */
                ENABLED,
                /**
                 * Do <i>NOT</i> persist to the command log.
                 */
                DISABLED;

                public boolean isEnabled() { return this == ENABLED; }
                public boolean isDisabled() { return this == DISABLED; }
            }

            /**
             * Whether the {@link CommandSubscriber} implementation
             * provided by this extension (which persists commands to the command log) is enabled or not.
             *
             * <p>
             *     One reason to use this option is if you wish to provide your own implementation that wraps
             *     or delegates to the default implementation of {@link CommandSubscriber} that is
             *     provided by the <i>commandlog</i> extension.  Because commands are published to
             *     <i>all</i> subscribers on the class path, you can disable the default implementation from
             *     doing anything using this setting.
             * </p>
             */
            private PersistPolicy persist = PersistPolicy.ENABLED;

            private final RunBackgroundCommands runBackgroundCommands = new RunBackgroundCommands();
            @Data
            public static class RunBackgroundCommands {

                /**
                 * If the attempt to execute the command in the background results in failure,
                 * what should the processing do?
                 */
                public enum OnFailurePolicy {
                    /**
                     * If a failure has occurred, then capture the exception on the <code>CommandLogEntry</code> and
                     * then move onto the next command
                     *
                     * <p>
                     *     This policy allows further processing to continue, but runs the risk that the failed command
                     *     is not noticed by anyone, producing perhaps erroneous results later if it is run
                     *     out-of-order.
                     * </p>
                     */
                    CONTINUE_WITH_NEXT,
                    /**
                     * If a failure has occurred, then leave the <code>CommandLogEntry</code> unchanged, so that it
                     * is picked up again to be retried.
                     *
                     * <p>
                     *     This policy is in effect a 'stop the line' or 'fail fast' approach.  Because the background
                     *     command only runs in serial, an exception will result in no other commands being executed.
                     * </p>
                     */
                    STOP_THE_LINE;
                }

                /**
                 * Limits the number of pending commands that the <code>RunBackgroundCommandsJob</code>
                 * will execute.  After these have been executed, any <code>RunBackgroundCommandsJobListener</code>s are called.
                 *
                 * <p>
                 *     By default, quartz runs this command every 10 seconds, so the size should be proportion to that.
                 * </p>
                 */
                private int batchSize = 25;

                /**
                 * If there is an exception executing one of the commands, what should be done?
                 */
                private OnFailurePolicy onFailurePolicy = OnFailurePolicy.STOP_THE_LINE;
            }
        }

        private final CommandReplay commandReplay = new CommandReplay();
        @Data
        public static class CommandReplay {

            private final PrimaryAccess primaryAccess = new PrimaryAccess();
            @Data
            public static class PrimaryAccess {
                @jakarta.validation.constraints.Pattern(regexp="^http[s]?://[^:]+?(:\\d+)?.*([^/]+/)$")
                private Optional<String> baseUrlRestful = Optional.empty();
                private Optional<String> user = Optional.empty();
                private Optional<String> password = Optional.empty();
                @jakarta.validation.constraints.Pattern(regexp="^http[s]?://[^:]+?(:\\d+)?.*([^/]+/)$")
                private Optional<String> baseUrlWicket = Optional.empty();
            }

            private final SecondaryAccess secondaryAccess = new SecondaryAccess();
            @Data
            public static class SecondaryAccess {
                @jakarta.validation.constraints.Pattern(regexp="^http[s]?://[^:]+?(:\\d+)?.*([^/]+/)$")
                private Optional<String> baseUrlWicket = Optional.empty();
            }

            private Integer batchSize = 10;

            private final QuartzSession quartzSession = new QuartzSession();
            @Data
            public static class QuartzSession {
                /**
                 * The user that runs the replay session secondary.
                 */
                private String user = "causewayModuleExtCommandReplaySecondaryUser";
                private List<String> roles = listOf("causewayModuleExtCommandReplaySecondaryRole");
            }

            private final QuartzReplicateAndReplayJob quartzReplicateAndReplayJob = new QuartzReplicateAndReplayJob();
            @Data
            public static class QuartzReplicateAndReplayJob {
                /**
                 * Number of milliseconds before starting the job.
                 */
                private long startDelay = 15000;
                /**
                 * Number of milliseconds before running again.
                 */
                private long repeatInterval = 10000;
            }

            private final Analyser analyser = new Analyser();
            @Data
            public static class Analyser {
                private final Result result = new Result();
                @Data
                public static class Result {
                    private boolean enabled = true;
                }
                private final Exception exception = new Exception();
                @Data
                public static class Exception {
                    private boolean enabled = true;
                }

            }
        }

        private final Cors cors = new Cors();
        @Data
        public static class Cors {

            /**
             * Whether the resource supports user credentials.
             *
             * <p>
             *     This flag is exposed as part of 'Access-Control-Allow-Credentials' header in a pre-flight response.
             *     It helps browser determine whether or not an actual request can be made using credentials.
             * </p>
             *
             * <p>
             *     By default this is not set (i.e. user credentials are not supported).
             * </p>
             *
             * <p>
             *     For more information, check the usage of the <code>cors.support.credentials</code> init parameter
             *     for <a href="https://github.com/eBay/cors-filter">EBay CORSFilter</a>.
             * </p>
             */
            private boolean allowCredentials = false;

            /**
             * Which origins are allowed to make CORS requests.
             *
             * <p>
             *     The default is the wildcard (&quot;*&quot;), meaning any origin is allowed to access the resource,
             *     but this can be made more restrictive if necessary using a whitelist of comma separated origins
             *     eg:
             * </p>
             * <p>
             *     <code>http://www.w3.org, https://www.apache.org</code>
             * </p>
             *
             * <p>
             *     For more information, check the usage of the <code>cors.allowed.origins</code> init parameter
             *     for <a href="https://github.com/eBay/cors-filter">EBay CORSFilter</a>.
             * </p>
             */
            private List<String> allowedOrigins = listOf("*");

            /**
             * Which HTTP headers can be allowed in a CORS request.
             *
             * <p>
             *     These header will also be returned as part of 'Access-Control-Allow-Headers' header in a
             *     pre-flight response.
             * </p>
             *
             * <p>
             *     For more information, check the usage of the <code>cors.allowed.headers</code> init parameter
             *     for <a href="https://github.com/eBay/cors-filter">EBay CORSFilter</a>.
             * </p>
             */
            private List<String> allowedHeaders = listOf(
                    "Content-Type",
                    "Accept",
                    "Origin",
                    "Access-Control-Request-Method",
                    "Access-Control-Request-Headers",
                    "Authorization",
                    "Cache-Control",
                    "If-Modified-Since",
                    "Pragma");

            /**
             * Which HTTP methods are permitted in a CORS request.
             *
             * <p>
             *     A comma separated list of HTTP methods that can be used to access the resource, using cross-origin
             *     requests. These are the methods which will also be included as part of 'Access-Control-Allow-Methods'
             *     header in a pre-flight response.
             * </p>
             *
             * <p>
             *     Default is <code>GET</code>, <code>POST</code>, <code>HEAD</code>, <code>OPTIONS</code>.
             * </p>
             *
             * <p>
             *     For more information, check the usage of the <code>cors.allowed.methods</code> init parameter
             *     for <a href="https://github.com/eBay/cors-filter">EBay CORSFilter</a>.
             * </p>
             */
            private List<String> allowedMethods = listOf("GET","PUT","DELETE","POST","OPTIONS");

            /**
             * Which HTTP headers are exposed in a CORS request.
             *
             * <p>
             *     A comma separated list of headers other than the simple response headers that browsers are allowed
             *     to access. These are the headers which will also be included as part of
             *     'Access-Control-Expose-Headers' header in the pre-flight response.
             * </p>
             *
             * <p>
             *     Default is none.
             * </p>
             *
             * <p>
             *     For more information, check the usage of the <code>cors.exposed.headers</code> init parameter
             *     for <a href="https://github.com/eBay/cors-filter">EBay CORSFilter</a>.
             * </p>
             */
            private List<String> exposedHeaders = listOf("Authorization");

        }

        private final ExecutionLog executionLog = new ExecutionLog();
        @Data
        public static class ExecutionLog {

            /**
             * As per {@link ExecutionLog#getPersist()}.
             *
             * <p>
             *     Implementation note: we use an enum here (rather than a simple boolean) to allow for future
             *     enhancements.
             * </p>
             */
            public enum PersistPolicy {
                /**
                 * Persist to the execution log.  This is the default.
                 */
                ENABLED,
                /**
                 * Do <i>NOT</i> persist to the execution log.
                 */
                DISABLED;

                public boolean isEnabled() { return this == ENABLED; }
                public boolean isDisabled() { return this == DISABLED; }
            }

            /**
             * Whether the {@link ExecutionSubscriber} implementation
             * provided by this extension (which persists executions to the execution log) is enabled or not.
             *
             * <p>
             *     One reason to use this option is if you wish to provide your own implementation that wraps
             *     or delegates to the default implementation of {@link ExecutionSubscriber} that is
             *     provided by the <i>executionLog</i> extension.  Because executions are published to
             *     <i>all</i> subscribers on the class path, you can disable the default implementation from
             *     doing anything using this setting.
             * </p>
             */
            private PersistPolicy persist = PersistPolicy.ENABLED;
        }

        private final ExecutionOutbox executionOutbox = new ExecutionOutbox();
        @Valid
        @Data
        public static class ExecutionOutbox {

            private final RestApi restApi = new RestApi();
            @Valid
            @Data
            public static class RestApi {
                /**
                 * The maximum number of interactions that will be returned when the REST API is polled.
                 */
                @Min(value = 1)
                @Max(value = 1000)
                private int maxPending = 100;
            }

            /**
             * As per {@link ExecutionLog#getPersist()}.
             *
             * <p>
             *     Implementation note: we use an enum here (rather than a simple boolean) to allow for future
             *     enhancements.
             * </p>
             */
            public enum PersistPolicy {
                /**
                 * Persist to the outbox.  This is the default.
                 */
                ENABLED,
                /**
                 * Do <i>NOT</i> persist to the outbox.
                 */
                DISABLED;

                public boolean isEnabled() { return this == ENABLED; }
                public boolean isDisabled() { return this == DISABLED; }
            }

            /**
             * Whether the {@link ExecutionSubscriber}
             * implementation provided by this extension (which persists executions to the outbox) is enabled or not.
             *
             * <p>
             *     One reason to use this option is if you wish to provide your own implementation that wraps
             *     or delegates to the outbox implementation of {@link ExecutionSubscriber} that is
             *     provided by the <i>executionOutbox</i> extension.  Because executions are published to
             *     <i>all</i> subscribers on the class path, you can disable the outbox implementation from
             *     doing anything using this setting.
             * </p>
             */
            private ExecutionOutbox.PersistPolicy persist = ExecutionOutbox.PersistPolicy.ENABLED;

        }

        private final LayoutLoaders layoutLoaders = new LayoutLoaders();
        @Data
        public static class LayoutLoaders {

            private final Github github = new Github();
            @Data
            public static class Github {

                /**
                 * eg <code>apache/causeway-app-simpleapp</code>
                 */
                private String repository;

                /**
                 * As per <a href="https://github.com/settings/tokens">https://github.com/settings/tokens</a>,
                 * must have permissions to the <code>/search</code> and <code>/contents</code> APIs for the specified
                 * {@link #getRepository() repository}.
                 */
                private String apiKey;
            }
        }

        private final Secman secman = new Secman();
        @Data
        public static class Secman {

            private final Seed seed = new Seed();
            @Data
            public static class Seed {

                public static final String ADMIN_USER_NAME_DEFAULT = "secman-admin";
                public static final String ADMIN_PASSWORD_DEFAULT = "pass";
                public static final String ADMIN_ROLE_NAME_DEFAULT = "causeway-ext-secman-admin";
                public static final List<String> ADMIN_STICKY_NAMESPACE_PERMISSIONS_DEFAULT =
                        Collections.unmodifiableList(listOf(
                                CausewayModuleApplib.NAMESPACE,
                                CausewayModuleApplib.NAMESPACE_SUDO,
                                CausewayModuleApplib.NAMESPACE_CONF,
                                CausewayModuleApplib.NAMESPACE_FEAT,
                                "causeway.security",
                                "causeway.ext.h2Console",
                                "causeway.ext.secman",
                                "causeway.ext.layoutLoaders"
                        ));
                public static final List<String> ADMIN_ADDITIONAL_NAMESPACE_PERMISSIONS =
                        Collections.unmodifiableList(listOf());
                public static final String REGULAR_USER_ROLE_NAME_DEFAULT = "causeway-ext-secman-user";
                public static final boolean AUTO_UNLOCK_IF_DELEGATED_AND_AUTHENTICATED_DEFAULT = false;

                /**
                 * Path to local YAML file, if present, to use as an alternative seeding strategy.
                 * <p>
                 * Eg. seed from a YAML file, that was previously exported by SecMan's
                 * ApplicationRoleManager_exportAsYaml mixin.
                 */
                private String yamlFile = null;

                private final Admin admin = new Admin();
                @Data
                public static class Admin {

                    /**
                     * The name of the security super user.
                     *
                     * <p>
                     * This user is automatically made a member of the
                     * {@link #getRoleName() admin role}, from which it is granted
                     * permissions to administer other users.
                     * </p>
                     *
                     * <p>
                     * The password for this user is set in {@link Admin#getPassword()}.
                     * </p>
                     *
                     * @see #getPassword()
                     * @see #getRoleName()
                     */
                    private String userName = ADMIN_USER_NAME_DEFAULT;

                    // sonar-ignore-on (detects potential security risk, which we are aware of)
                    /**
                     * The corresponding password for {@link #getUserName() admin user}.
                     *
                     * @see #getUserName()
                     */
                    private String password = ADMIN_PASSWORD_DEFAULT;
                    // sonar-ignore-off

                    /**
                     * The name of security admin role.
                     *
                     * <p>
                     * Users with this role (in particular, the default
                     * {@link Admin#getUserName() admin user} are granted access to a set of
                     * namespaces ({@link NamespacePermissions#getSticky()} and
                     * {@link NamespacePermissions#getAdditional()}) which are intended to
                     * be sufficient to allow users with this admin role to be able to
                     * administer the security module itself, for example to manage users and
                     * roles.
                     * </p>
                     *
                     * @see Admin#getUserName()
                     * @see NamespacePermissions#getSticky()
                     * @see NamespacePermissions#getAdditional()
                     */
                    private String roleName = ADMIN_ROLE_NAME_DEFAULT;

                    private final NamespacePermissions namespacePermissions = new NamespacePermissions();
                    @Data
                    public static class NamespacePermissions {

                        /**
                         * The set of namespaces to which the {@link Admin#getRoleName() admin role}
                         * is granted.
                         *
                         * <p>
                         * These namespaces are intended to be sufficient to allow users with
                         * this admin role to be able to administer the security module itself,
                         * for example to manage users and roles.  The security user is not
                         * necessarily able to use the main business logic within the domain
                         * application itself, though.
                         * </p>
                         *
                         * <p>
                         * These roles cannot be removed via user interface
                         * </p>
                         *
                         * <p>
                         * WARNING: normally these should not be overridden.  Instead, specify
                         * additional namespaces using
                         * {@link NamespacePermissions#getAdditional()}.
                         * </p>
                         *
                         * @see NamespacePermissions#getAdditional()
                         */
                        private List<String> sticky = ADMIN_STICKY_NAMESPACE_PERMISSIONS_DEFAULT;

                        /**
                         * An (optional) additional set of namespaces that the
                         * {@link Admin#getRoleName() admin role} is granted.
                         *
                         * <p>
                         * These are in addition to the main
                         * {@link NamespacePermissions#getSticky() namespaces} granted.
                         * </p>
                         *
                         * @see NamespacePermissions#getSticky()
                         */
                        private List<String> additional = ADMIN_ADDITIONAL_NAMESPACE_PERMISSIONS;

                    }

                }

                private final RegularUser regularUser = new RegularUser();
                @Data
                public static class RegularUser {

                    /**
                     * The role name for regular users of the application, granting them access
                     * to basic security features.
                     *
                     * <p>
                     *     The exact set of permissions is hard-wired in the
                     *     <code>CausewayExtSecmanRegularUserRoleAndPermissions</code> fixture.
                     * </p>
                     */
                    private String roleName = REGULAR_USER_ROLE_NAME_DEFAULT;

                }

            }

            private final FixtureScripts fixtureScripts = new FixtureScripts();
            @Data
            public static class FixtureScripts {

                private final AbstractRoleAndPermissionsFixtureScript abstractRoleAndPermissionsFixtureScript = new AbstractRoleAndPermissionsFixtureScript();
                @Data
                public static class AbstractRoleAndPermissionsFixtureScript {

                    public enum UnknownFeatureIdCheckingPolicy {
                        /**
                         * Do not check whether the featureIds passed in actually exist.
                         */
                        IGNORE,
                        /**
                         * Check that the featureIds passed in actually exist, and fail immediately if not.
                         */
                        FAIL_FAST,
                        ;

                        public boolean isIgnore() { return this == IGNORE; }
                        public boolean isFailFast() { return this == FAIL_FAST; }
                    }

                    /**
                     * Whether to check if every featureId passed in exists or not.
                     */
                    private UnknownFeatureIdCheckingPolicy unknownFeatureIdCheckingPolicy = UnknownFeatureIdCheckingPolicy.IGNORE;
                }

            }

            private final DelegatedUsers delegatedUsers = new DelegatedUsers();

            @Data
            public static class DelegatedUsers {

                public enum AutoCreatePolicy {
                    AUTO_CREATE_AS_LOCKED,
                    AUTO_CREATE_AS_UNLOCKED,
                    DO_NOT_AUTO_CREATE,
                }

                /**
                 * Whether delegated users should be autocreated as locked (the default) or unlocked.
                 *
                 * <p>
                 * BE AWARE THAT if any users are auto-created as unlocked, then the set of roles that
                 * they are given should be highly restricted !!!
                 * </p>
                 *
                 * <p>
                 *     NOTE also that this configuration policy is ignored if running secman with Spring OAuth2
                 *     or Keycloak as the authenticator; users are always auto-created.
                 * </p>
                 */
                private AutoCreatePolicy autoCreatePolicy = AutoCreatePolicy.AUTO_CREATE_AS_LOCKED;

                /**
                 * The set of roles that users that have been automatically created are granted automatically.
                 *
                 * <p>
                 *     Typically the regular user role (as per <code>causeway.secman.seed.regular-user.role-name</code>,
                 *     default value of <code>causeway-ext-secman-user</code>) will be one of the roles listed here, to
                 *     provide the ability for the end-user to logout, among other things (!).
                 * </p>
                 */
                private List<String> initialRoleNames = new ArrayList<>();

            }

            public enum PermissionsEvaluationPolicy {
                ALLOW_BEATS_VETO,
                VETO_BEATS_ALLOW
            }

            /**
             * If there are conflicting (allow vs veto) permissions at the same scope, then this policy determines
             * whether to prefer to allow the permission or to veto it.
             *
             * <p>
             *     This is only used an implementation of secman's <code>PermissionsEvaluationService</code> SPI has
             *     not been provided explicitly.
             * </p>
             */
            private PermissionsEvaluationPolicy permissionsEvaluationPolicy = PermissionsEvaluationPolicy.ALLOW_BEATS_VETO;

            private final UserRegistration userRegistration = new UserRegistration();
            @Data
            public static class UserRegistration {
                /**
                 * The set of roles that users registering with the app are granted
                 * automatically.
                 *
                 * <p>
                 *     If using the wicket viewer, also requires
                 *     {@link Viewer.Wicket#isSuppressSignUp() causeway.viewer.wicket.suppress-signup} to be set
                 *     <code>false</code>, along with any other of its other prereqs.
                 * </p>
                 */
                private final List<String> initialRoleNames = new ArrayList<>();
            }

            public enum UserMenuMeActionPolicy {
                HIDE,
                DISABLE,
                ENABLE
            }

            /**
             * Whether the presence of SecMan should result in the automatic suppression of the
             * {@link org.apache.causeway.applib.services.userui.UserMenu}'s {@link UserMenu.me#act() me()} action.
             *
             * <p>
             *     This is normally what is required as SecMan's <code>ApplicationUser</code> is a more comprehensive
             *     representation of the current user.  If the default {@link UserMenu.me#act() me()} action is not
             *     suppressed, then the end-user will see two actions with the name &quot;me&quot; in the tertiary menu.
             * </p>
             */
            private UserMenuMeActionPolicy userMenuMeActionPolicy = UserMenuMeActionPolicy.HIDE;
        }

        private final SessionLog sessionLog = new SessionLog();
        @Data
        public static class SessionLog {
            boolean autoLogoutOnRestart = true;
        }

        private final Titlecache titlecache = new Titlecache();
        @Data
        public static class Titlecache {

            private final Caffeine caffeine = new Caffeine();
            @Data
            public static class Caffeine {
                /**
                 * Default duration that entries remain in the cache (for a given logical type name),
                 * in minutes.
                 *
                 * <p>
                 *     Default is 20 mins.
                 * </p>
                 */
                private int expiryDurationInMinutes = 20;

                /**
                 * Default maximum number of entries in the cache (for a given logical type name)
                 *
                 * <p>
                 *     Default is 1000
                 * </p>
                 */
                private int maxSizeInEntries = 1000;
            }
        }
    }

    private static List<String> listOf(final String ...values) {
        return new ArrayList<>(Arrays.asList(values));
    }

    record PatternToString(
            Pattern pattern,
            String string) {
    }
    private static Map<Pattern, String> asMap(final String... mappings) {
        return new LinkedHashMap<>(_NullSafe.stream(mappings).map(mapping -> {
                    final String[] parts = mapping.split(":");
                    if (parts.length != 2) return null;
                    
                    try {
                        return new PatternToString(Pattern.compile(parts[0], Pattern.CASE_INSENSITIVE), parts[1]);
                    } catch(Exception ex) {
                        return null;
                    }
                }).filter(Objects::nonNull)
                .collect(Collectors.toMap(PatternToString::pattern, PatternToString::string)));
    }

    @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
    @Retention(RUNTIME)
    @Constraint(validatedBy = AssignableFromValidator.class)
    @Documented
    public @interface AssignableFrom {

        String value();

        String message()
                default "{org.apache.causeway.core.config.CausewayConfiguration.AssignableFrom.message}";

        Class<?>[] groups() default { };

        Class<? extends Payload>[] payload() default { };
    }

    public static class AssignableFromValidator implements ConstraintValidator<AssignableFrom, Class<?>> {

        private Class<?> superType;

        @Override
        public void initialize(final AssignableFrom assignableFrom) {
            var className = assignableFrom.value();
            try {
                superType = _Context.loadClass(className);
            } catch (ClassNotFoundException e) {
                superType = null;
            }
        }

        @Override
        public boolean isValid(
                final Class<?> candidateClass,
                final ConstraintValidatorContext constraintContext) {
            if (superType == null || candidateClass == null) {
                return true;
            }
            return superType.isAssignableFrom(candidateClass);
        }
    }

    @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
    @Retention(RUNTIME)
    @Constraint(validatedBy = OneOfValidator.class)
    @Documented
    public @interface OneOf {

        String[] value();

        String message()
                default "{org.apache.causeway.core.config.CausewayConfiguration.OneOf.message}";

        Class<?>[] groups() default { };

        Class<? extends Payload>[] payload() default { };
    }

    public static class OneOfValidator implements ConstraintValidator<OneOf, String> {

        private List<String> allowed;

        @Override
        public void initialize(final OneOf assignableFrom) {
            var value = assignableFrom.value();
            allowed = value != null? Collections.unmodifiableList(Arrays.asList(value)): Collections.emptyList();
        }

        @Override
        public boolean isValid(
                final String candidateValue,
                final ConstraintValidatorContext constraintContext) {
            return allowed.contains(candidateValue);
        }
    }

}
