From ab2b02ac8d4dfe32d7f7e0bd6428aa27f494343b Mon Sep 17 00:00:00 2001
From: "Matteo E. Minnai"
Date: Thu, 6 Aug 2026 19:58:44 +0200
Subject: [PATCH 1/2] ESB-1133 SOLR boolean index
---
README.md | 155 +-
.../apsadmin/global-messages_en.properties | 4 +
.../apsadmin/global-messages_it.properties | 4 +
.../entity/AbstractApsEntityFinderAction.java | 88 +-
.../system/entity/EntityActionHelper.java | 133 +-
...stractBaseEntityAttributeConfigAction.java | 25 +-
.../type/CompositeAttributeConfigAction.java | 16 +
.../entity/type/EntityTypeConfigAction.java | 53 +-
.../system/entity/type/package_en.properties | 3 +
.../system/entity/type/package_it.properties | 3 +
...attribute-type-entry-composite-element.jsp | 17 +
.../entity/attribute-type-entry-composite.jsp | 23 +-
.../apsadmin/jsp/entity/entityFinding.jsp | 32 +-
.../entity/include/attribute-flag-cell.jsp | 31 +
.../jsp/entity/include/attribute-list.jsp | 93 +-
.../include/threeStateAttributeInputField.jsp | 2 +-
.../WEB-INF/apsadmin/jsp/user/user-list.jsp | 98 +-
.../EntityActionHelperNestedSearchTest.java | 183 +++
.../CompositeAttributeConfigActionTest.java | 175 ++-
.../type/EntityTypeConfigActionTest.java | 95 +-
advcontentsearch.postman_collection.json | 1365 +++++++++++++++++
.../services/content/ContentManager.java | 6 +-
.../content/widget/UserFilterOptionBean.java | 16 +-
.../services/resource/ResourceManager.java | 11 +
.../services/searchengine/IndexerDAO.java | 42 +-
.../liquibase/jacms/changeSetPort.xml | 4 +
.../port/00000000000001_dataPort_test.xml | 84 +
...0803000000_jacms_widen_search_attrname.xml | 29 +
...00_jacms_boolean_userfilter_any_option.xml | 78 +
.../port/clob/production/guifragment_12_2.ftl | 24 +
.../port/clob/production/guifragment_14_2.ftl | 24 +
.../port/clob/production/guifragment_15_2.ftl | 24 +
.../jacms/port/clob/test/contents_50.xml | 26 +
.../jacms/port/clob/test/contents_51.xml | 26 +
.../jacms/port/clob/test/contents_52.xml | 26 +
.../jacms/port/clob/test/contents_53.xml | 22 +
.../jacms/port/clob/test/sysconfig_1.xml | 17 +
.../inc/userFilter-module-entity-Boolean.jsp | 20 +-
.../inc/userFilter-module-entity-CheckBox.jsp | 26 +-
.../userFilter-module-entity-ThreeState.jsp | 23 +-
.../apsadmin/jsp/content/contentFinding.jsp | 138 +-
.../contentListViewer/entryFilter.jsp | 41 +-
.../services/content/ContentManagerTest.java | 23 +-
.../services/content/TestContentManager.java | 241 ++-
.../entity/TestContentEntityManager.java | 2 +-
.../widget/UserFilterOptionBeanTest.java | 56 +-
.../resource/ResourceManagerTest.java | 41 +-
.../services/searchengine/IndexerDAOTest.java | 198 +++
.../content/TestContentFinderAction.java | 133 +-
.../attribute/TestContentLinkAction.java | 8 +-
.../TestHypertextAttributeAction.java | 8 +-
.../viewer/TestContentFinderViewerAction.java | 8 +-
.../entity/TestJacmsEntityManagersAction.java | 8 +-
.../ContentControllerIntegrationTest.java | 109 +-
.../ContentTypeResourceIntegrationTest.java | 146 ++
.../content/TestContentFinderAction.java | 4 +-
.../content/TestIntroNewContentAction.java | 2 +-
.../common/entity/AbstractEntityDAO.java | 57 +-
.../common/entity/ApsEntityManager.java | 141 +-
.../system/common/entity/IEntityManager.java | 29 +
.../entity/model/EntitySearchFilter.java | 11 +
.../model/attribute/AttributeInterface.java | 69 +
.../model/attribute/BooleanAttribute.java | 28 +
.../model/attribute/CompositeAttribute.java | 8 +-
.../entity/model/attribute/DateAttribute.java | 13 +
.../model/attribute/NumberAttribute.java | 17 +
.../model/attribute/ThreeStateAttribute.java | 30 +
.../entity/search/EntitySearchKeys.java | 92 ++
.../entity/search/EntitySearchSchema.java | 113 ++
.../entity/search/NestedSearchSupport.java | 499 ++++++
.../common/entity/search/SearchFieldType.java | 59 +
.../entity/search/SearchRecordSpec.java | 27 +
.../entity/search/SearchableAttributeRef.java | 92 ++
.../entity/AbstractEntityTypeService.java | 90 ++
.../AbstractEntityTypeValidator.java | 11 +
.../resources/liquibase/changeSetServ.xml | 2 +
.../serv/00000000000005_schemaServ.xml | 23 +
.../main/resources/rest/messages.properties | 6 +
.../AbstractEntityDAONestedSearchTest.java | 353 +++++
.../ApsEntityManagerNestedSearchableTest.java | 205 +++
.../EntitySearchFilterNestedSearchTest.java | 114 ++
.../attribute/AttributeSearchFieldTest.java | 164 ++
.../CompositeAttributeXmlConfigTest.java | 137 ++
.../entity/search/EntitySearchKeysTest.java | 217 +++
.../entity/search/EntitySearchSchemaTest.java | 151 ++
.../search/NestedSearchSupportTest.java | 845 ++++++++++
.../search/SearchableAttributeRefTest.java | 123 ++
.../5_POST_composite_listFilter_invalid.json | 28 +
.../5_POST_composite_listFilter_valid.json | 37 +
.../ProfileTypeControllerIntegrationTest.java | 58 +
.../servlet/security/BasicAuthFilterTest.java | 20 +-
run-reactor-tests.sh | 256 ++++
.../content/AdvContentFacetManager.java | 52 +-
.../jpsolr/aps/system/solr/IndexerDAO.java | 107 +-
.../jpsolr/aps/system/solr/SearcherDAO.java | 13 +-
.../aps/system/solr/SolrFieldsChecker.java | 53 +-
.../system/solr/SolrSearchEngineManager.java | 50 +-
.../solr/model/ContentTypeSettings.java | 61 +-
.../aps/system/solr/model/SolrFields.java | 34 +
.../content/AdvContentFacetManagerTest.java | 95 +-
.../aps/system/solr/IndexerDAOTest.java | 346 +++++
.../aps/system/solr/SearcherDAOTest.java | 164 +-
.../system/solr/SolrFieldsCheckerTest.java | 233 ++-
.../solr/SolrSearchEngineManagerTest.java | 244 ++-
.../solr/model/ContentTypeSettingsTest.java | 235 +++
.../AdvContentSearchControllerTest.java | 4 +-
.../jpwebdynamicform/changeSetServ.xml | 2 +
...00003_schemaServ_widen_search_attrname.xml | 31 +
108 files changed, 9577 insertions(+), 564 deletions(-)
create mode 100644 admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/entity/include/attribute-flag-cell.jsp
create mode 100644 admin-console/src/test/java/com/agiletec/apsadmin/system/entity/EntityActionHelperNestedSearchTest.java
create mode 100644 advcontentsearch.postman_collection.json
create mode 100644 cms-plugin/src/main/resources/liquibase/jacms/port/20260803000000_jacms_widen_search_attrname.xml
create mode 100644 cms-plugin/src/main/resources/liquibase/jacms/port/20260807000000_jacms_boolean_userfilter_any_option.xml
create mode 100644 cms-plugin/src/main/resources/liquibase/jacms/port/clob/production/guifragment_12_2.ftl
create mode 100644 cms-plugin/src/main/resources/liquibase/jacms/port/clob/production/guifragment_14_2.ftl
create mode 100644 cms-plugin/src/main/resources/liquibase/jacms/port/clob/production/guifragment_15_2.ftl
create mode 100644 cms-plugin/src/main/resources/liquibase/jacms/port/clob/test/contents_50.xml
create mode 100644 cms-plugin/src/main/resources/liquibase/jacms/port/clob/test/contents_51.xml
create mode 100644 cms-plugin/src/main/resources/liquibase/jacms/port/clob/test/contents_52.xml
create mode 100644 cms-plugin/src/main/resources/liquibase/jacms/port/clob/test/contents_53.xml
create mode 100644 cms-plugin/src/test/java/com/agiletec/plugins/jacms/aps/system/services/searchengine/IndexerDAOTest.java
create mode 100644 engine/src/main/java/org/entando/entando/aps/system/common/entity/search/EntitySearchKeys.java
create mode 100644 engine/src/main/java/org/entando/entando/aps/system/common/entity/search/EntitySearchSchema.java
create mode 100644 engine/src/main/java/org/entando/entando/aps/system/common/entity/search/NestedSearchSupport.java
create mode 100644 engine/src/main/java/org/entando/entando/aps/system/common/entity/search/SearchFieldType.java
create mode 100644 engine/src/main/java/org/entando/entando/aps/system/common/entity/search/SearchRecordSpec.java
create mode 100644 engine/src/main/java/org/entando/entando/aps/system/common/entity/search/SearchableAttributeRef.java
create mode 100644 engine/src/main/resources/liquibase/serv/00000000000005_schemaServ.xml
create mode 100644 engine/src/test/java/com/agiletec/aps/system/common/entity/AbstractEntityDAONestedSearchTest.java
create mode 100644 engine/src/test/java/com/agiletec/aps/system/common/entity/ApsEntityManagerNestedSearchableTest.java
create mode 100644 engine/src/test/java/com/agiletec/aps/system/common/entity/model/EntitySearchFilterNestedSearchTest.java
create mode 100644 engine/src/test/java/com/agiletec/aps/system/common/entity/model/attribute/AttributeSearchFieldTest.java
create mode 100644 engine/src/test/java/com/agiletec/aps/system/common/entity/model/attribute/CompositeAttributeXmlConfigTest.java
create mode 100644 engine/src/test/java/org/entando/entando/aps/system/common/entity/search/EntitySearchKeysTest.java
create mode 100644 engine/src/test/java/org/entando/entando/aps/system/common/entity/search/EntitySearchSchemaTest.java
create mode 100644 engine/src/test/java/org/entando/entando/aps/system/common/entity/search/NestedSearchSupportTest.java
create mode 100644 engine/src/test/java/org/entando/entando/aps/system/common/entity/search/SearchableAttributeRefTest.java
create mode 100644 engine/src/test/java/org/entando/entando/web/userprofile/5_POST_composite_listFilter_invalid.json
create mode 100644 engine/src/test/java/org/entando/entando/web/userprofile/5_POST_composite_listFilter_valid.json
create mode 100755 run-reactor-tests.sh
create mode 100644 solr-plugin/src/test/java/org/entando/entando/plugins/jpsolr/aps/system/solr/IndexerDAOTest.java
create mode 100644 webdynamicform-plugin/src/main/resources/liquibase/jpwebdynamicform/serv/00000000000003_schemaServ_widen_search_attrname.xml
diff --git a/README.md b/README.md
index e7113c6878..51b30600d0 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,11 @@ The Content Scheduler, Content Workflow, and Web Dynamic Form plugins are disabl
## Testing
+The test suite runs under the `pre-deployment-verification` Maven profile — without it, surefire
+is skipped and no tests execute.
+
+### Quick commands
+
To execute all the tests:
```
@@ -32,41 +37,119 @@ To execute a specific test:
mvn clean test -Ppre-deployment-verification -pl -Dtest=
```
-By default the logging output in tests is minimized.
-The general log level is controlled by the variable `ROOT_LOG_LEVEL`, that in tests is set to `WARN` by default.
+### Reactor test runner (`run-reactor-tests.sh`)
+
+To run the tests of one or more reactor modules, the repo ships a helper at the project root that
+wraps the Maven invocation, chooses the right build strategy, and prints a per-module PASS/FAIL
+summary (logs are saved under `test-results/`).
+
+```
+# interactive module picker (UP/DOWN move, SPACE select, a=all, n=none, ENTER confirm, q quit)
+./run-reactor-tests.sh
+
+# non-interactive: test only the given module(s)
+./run-reactor-tests.sh engine cms-plugin
+
+# test every module, no prompt
+ASSUME_YES=1 ./run-reactor-tests.sh
+```
+
+When a **subset** of modules is selected, the script first builds the selected modules and their
+upstream dependencies **without** tests (`install -DskipTests`), then runs the tests for the
+selected modules only (no `-am`, so dependencies are not re-tested). Selecting all modules runs the
+whole reactor in a single pass.
+
+Environment overrides:
+
+| Variable | Default | Effect |
+| :-- | :-- | :-- |
+| `PROFILE` | `pre-deployment-verification` | Maven profile that enables the tests |
+| `MVN_OPTS` | _(empty)_ | extra Maven options, e.g. `-o` for offline |
+| `ASSUME_YES` | `0` | skip the picker and test every module |
+| `DRY_RUN` | `0` | print the Maven command(s) without running them |
+
+In a non-interactive shell (CI or a pipe) the picker is skipped automatically and all modules are
+tested.
+
+By default the logging output in tests is minimized. See [Logging](#logging) below for how to get
+verbose/`DEBUG` output, both for the running webapp and for test runs (they work differently).
+
+## Logging
+
+Logging is configured via `engine/src/main/resources/base.xml` (logback) and driven by two environment
+variables:
+- `ROOT_LOG_LEVEL` — the root logger level. Defaults to `DEBUG` when running the webapp; overridden to
+ `WARN` when running tests (see `pom.xml` surefire configuration).
+- `LOG_LEVEL` — the console (`STDOUT`) appender threshold. Defaults to `WARN`, regardless of
+ `ROOT_LOG_LEVEL`.
+
+To run the webapp locally with `DEBUG` logs printed to the console:
+
+```
+cd webapp/
+LOG_LEVEL=DEBUG mvn package jetty:run-war -Pjetty-local -Dspring.profiles.active=swagger -DskipTests -DskipLicenseDownload -Pderby -Pkeycloak
+```
+
+`ROOT_LOG_LEVEL` does not need to be set for this, since it already defaults to `DEBUG` outside of tests;
+`LOG_LEVEL` is the variable that actually gates what reaches the console.
+
+Test runs are different: `entando-engine`'s test-jar ships `logback-test.xml`, which every other module
+picks up on its test classpath. It hardcodes `` (with explicit per-package `DEBUG`
+overrides only for a couple of Spring test loggers), so **`ROOT_LOG_LEVEL`/`LOG_LEVEL` have no effect on
+test runs** — only on the running webapp. To get `DEBUG` output from a test run, point Logback at a
+throwaway config instead:
+
+```
+cat > /tmp/logback-debug.xml <<'EOF'
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+EOF
+mvn clean test -Ppre-deployment-verification -pl -Dtest= \
+ -DargLine=-Dlogback.configurationFile=/tmp/logback-debug.xml
+```
+
+(Alternatively, add a one-off `` line to
+`engine/src/test/resources/logback-test.xml` before running the test — cheaper for a quick, throwaway
+check, but remember to revert it since it's a shared test resource.)
## Environment Variables List
-| Group | Name | Value [default] | Description |
-| :-- | :-- | :-- | :---------------- |
-|CDS | CDS_ENABLED | true, [false] | Enable Content Delivery Server |
-|| CDS_PUBLIC_URL | http://YOUR-APP-NAME-cds.YOUR-HOST-NAME/YOUR-TENANT-ID | |
-|| CDS_PRIVATE_URL | http://YOUR-TENANT-ID-cds-service:8080 | |
-|| CDS_PATH | /api/v1 ||
-| Keycloak/TLS | KEYCLOAK_AUTH_URL | https://YOUR-HOST-NAME/auth|
-|| SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER_URI| https://YOUR-HOST-NAME/auth/realms/entando ||
-|| ENTANDO_APP_USE_TLS | | protocol for the redirect to keycloak login |
-|| ENTANDO_APP_ENGINE_EXTERNAL_PORT | | to force the port to use |
-|Redis server | REDIS_ACTIVE | true, [false] | to activate Redis cache management |
-|| REDIS_ADDRESS |URL [redis://localhost:6379]| Redis host address||
-|| REDIS_ADDRESSES | | for HA, insert the comma separated list of nodes |
-|| REDIS_MASTER_NAME | [mymaster] | To specify the name of the master node |
-|| REDIS_SESSION_ACTIVE | true, [false] | enable storing of HTTP sessions in the Redis cluster, REDIS_ACTIVE has to be "true" too.
-|| REDIS_PASSWORD | | |
-|| REDIS_USE_SENTINEL_EVENTS | [true], false | when Redis is active and Redis addresses is set, use Sentinel Monitoring
-|| REDIS_IO_THREAD_POOL_SIZE | Integer, [8] | to mitigate errors caused by missing front-end cache refresh
-|Solr | SOLR_ACTIVE | true, false | to activate Solr search
-|| SOLR_ADDRESS |[http://localhost:8983/solr] | Solr host address
-|| SOLR_CORE | string, [entando] | name of collection
-|| advancedSearch | true, false | To add the Solr config page to the CMS menu
-| Tomcat server | AGENT_ENABLED | true, [false] | if true, adds the agent options to tomcat
-|| AGENT_OPTS | javaagent:~/YOUR-JARFILE.jar, [empty] | the jar file with the agent options to use
-|| TOMCAT_MAX_POST_SIZE | Enter a value in bytes, [209,715,200 bytes] | to configure connector maxPostSize |
-|| FILE_UPLOAD_MAX_SIZE | Enter a value in bytes, [52,428,800 bytes] | to configure the application upload limit
-| MISC | | | |
-|| ENTANDO_BUNDLE_CLI_ETC | ${ENTANDO_BUNDLE_CLI_ETC}/hub/credentials | Credentials/parameters saved within JSON files under this path for ent bundle add hub command
-|| ENTANDO_APP_ENGINE_HEALTH_CHECK_TYPE | db.migration.strategy | [auto], skip, disabled, generate_sql | Liquibase strategy
-|| LOG_CONFIG_FILE_PATH | | to use the logback composable feature |
-|| ENTANDO_DOCKER_REGISTRY_OVERRIDE | | Deprecated-for v1 bundles, to propagate to CM for plugins |
-| Feature Flags | ENTANDO_FEATURE_FLAGS | comma-separated list of tags | Enable experimental features. Example: `CACHE_PIPELINE,HEADLESS_WIDGET_CONFIG` |
-|| | | Available flags: `CACHE_PIPELINE`, `HEADLESS_WIDGET_CONFIG` |
-|| ENTANDO_FF_DEEP_DEBUG | comma-separated list of tags | Enable deep debug logging for specific components. Example: `service-reload` |
+| Group | Name | Value [default] | Description |
+|:--------------|:-------------------------------------------------------|:-------------------------------------------------------|:----------------------------------------------------------------------------------------------|
+| CDS | CDS_ENABLED | true, [false] | Enable Content Delivery Server |
+| | CDS_PUBLIC_URL | http://YOUR-APP-NAME-cds.YOUR-HOST-NAME/YOUR-TENANT-ID | |
+| | CDS_PRIVATE_URL | http://YOUR-TENANT-ID-cds-service:8080 | |
+| | CDS_PATH | /api/v1 | |
+| Keycloak/TLS | KEYCLOAK_AUTH_URL | https://YOUR-HOST-NAME/auth | |
+| | SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER_URI | https://YOUR-HOST-NAME/auth/realms/entando | |
+| | ENTANDO_APP_USE_TLS | | protocol for the redirect to keycloak login |
+| | ENTANDO_APP_ENGINE_EXTERNAL_PORT | | to force the port to use |
+| Redis server | REDIS_ACTIVE | true, [false] | to activate Redis cache management |
+| | REDIS_ADDRESS | URL [redis://localhost:6379] | Redis host address ||
+| | REDIS_ADDRESSES | | for HA, insert the comma separated list of nodes |
+| | REDIS_MASTER_NAME | [mymaster] | To specify the name of the master node |
+| | REDIS_SESSION_ACTIVE | true, [false] | enable storing of HTTP sessions in the Redis cluster, REDIS_ACTIVE has to be "true" too. |
+| | REDIS_PASSWORD | | |
+| | REDIS_USE_SENTINEL_EVENTS | [true], false | when Redis is active and Redis addresses is set, use Sentinel Monitoring |
+| | REDIS_IO_THREAD_POOL_SIZE | Integer, [8] | to mitigate errors caused by missing front-end cache refresh |
+| Solr | SOLR_ACTIVE | true, false | to activate Solr search |
+| | SOLR_ADDRESS | [http://localhost:8983/solr] | Solr host address |
+| | SOLR_CORE | string, [entando] | name of collection |
+| | advancedSearch | true, false | To add the Solr config page to the CMS menu |
+| Tomcat server | AGENT_ENABLED | true, [false] | if true, adds the agent options to tomcat |
+| | AGENT_OPTS | javaagent:~/YOUR-JARFILE.jar, [empty] | the jar file with the agent options to use |
+| | TOMCAT_MAX_POST_SIZE | Enter a value in bytes, [209,715,200 bytes] | to configure connector maxPostSize |
+| | FILE_UPLOAD_MAX_SIZE | Enter a value in bytes, [52,428,800 bytes] | to configure the application upload limit |
+| MISC | | | |
+| | ENTANDO_BUNDLE_CLI_ETC | ${ENTANDO_BUNDLE_CLI_ETC}/hub/credentials | Credentials/parameters saved within JSON files under this path for ent bundle add hub command |
+| | ENTANDO_APP_ENGINE_HEALTH_CHECK_TYPE | db.migration.strategy | [auto], skip, disabled, generate_sql | Liquibase strategy
+| | LOG_CONFIG_FILE_PATH | | to use the logback composable feature |
+| | ENTANDO_DOCKER_REGISTRY_OVERRIDE | | Deprecated-for v1 bundles, to propagate to CM for plugins |
+| Feature Flags | ENTANDO_FEATURE_FLAGS | comma-separated list of tags | Enable experimental features. Example: `CACHE_PIPELINE,HEADLESS_WIDGET_CONFIG` |
+| | | | Available flags: `CACHE_PIPELINE`, `HEADLESS_WIDGET_CONFIG` |
+| | ENTANDO_FF_DEEP_DEBUG | comma-separated list of tags | Enable deep debug logging for specific components. Example: `service-reload` |
diff --git a/admin-console/src/main/java/com/agiletec/apsadmin/global-messages_en.properties b/admin-console/src/main/java/com/agiletec/apsadmin/global-messages_en.properties
index f0023ab965..a8055ae3a9 100644
--- a/admin-console/src/main/java/com/agiletec/apsadmin/global-messages_en.properties
+++ b/admin-console/src/main/java/com/agiletec/apsadmin/global-messages_en.properties
@@ -95,6 +95,8 @@ label.no=No
label.true=True
label.false=False
label.bothYesAndNo=Both
+label.any=Any
+label.notSet=Not set
label.state=Status
label.all=All
label.confirm=Confirm
@@ -319,6 +321,8 @@ Entity.attribute.flag.mandatory.full=Mandatory
Entity.attribute.flag.mandatory.short=*
Entity.attribute.flag.searchable.full=Can be used as a filter in lists
Entity.attribute.flag.searchable.short=F
+Entity.attribute.flag.searchable.notApplicable.type=Not available for this attribute type
+Entity.attribute.flag.searchable.notApplicable.list=Not available for attributes inside a list
#deprecated - start
Entity.attribute.flag.searcheable.full=Can be used as a filter in lists
Entity.attribute.flag.searcheable.short=F
diff --git a/admin-console/src/main/java/com/agiletec/apsadmin/global-messages_it.properties b/admin-console/src/main/java/com/agiletec/apsadmin/global-messages_it.properties
index eb02d85b33..4a8c49af13 100644
--- a/admin-console/src/main/java/com/agiletec/apsadmin/global-messages_it.properties
+++ b/admin-console/src/main/java/com/agiletec/apsadmin/global-messages_it.properties
@@ -102,6 +102,8 @@ label.no=No
label.true=Vero
label.false=Falso
label.bothYesAndNo=Indifferente
+label.any=Qualsiasi
+label.notSet=Non impostato
label.state=Stato
label.all=Tutti
label.confirm=Conferma
@@ -328,6 +330,8 @@ Entity.attribute.flag.mandatory.full=Obbligatorio
Entity.attribute.flag.mandatory.short=*
Entity.attribute.flag.searchable.full=Utilizzabile come filtro nelle liste
Entity.attribute.flag.searchable.short=F
+Entity.attribute.flag.searchable.notApplicable.type=Non disponibile per questo tipo di attributo
+Entity.attribute.flag.searchable.notApplicable.list=Non disponibile per gli attributi dentro una lista
#deprecated labels - start
Entity.attribute.flag.searcheable.full=Utilizzabile come filtro nelle liste
Entity.attribute.flag.searcheable.short=F
diff --git a/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/AbstractApsEntityFinderAction.java b/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/AbstractApsEntityFinderAction.java
index 54c9992b5e..5bd4db7fd9 100644
--- a/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/AbstractApsEntityFinderAction.java
+++ b/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/AbstractApsEntityFinderAction.java
@@ -23,7 +23,10 @@
import org.entando.entando.ent.util.EntLogging.EntLogger;
import org.entando.entando.ent.util.EntLogging.EntLogFactory;
+import org.entando.entando.aps.system.common.entity.search.EntitySearchKeys;
+import org.entando.entando.aps.system.common.entity.search.EntitySearchSchema;
import com.agiletec.aps.system.common.entity.IEntityManager;
+import org.entando.entando.aps.system.common.entity.search.SearchableAttributeRef;
import com.agiletec.aps.system.common.entity.model.EntitySearchFilter;
import com.agiletec.aps.system.common.entity.model.IApsEntity;
import com.agiletec.aps.system.common.entity.model.attribute.AttributeInterface;
@@ -175,27 +178,98 @@ public String getSearchFormFieldValue(String inputFieldName) {
return val;
}
+ /**
+ * @return the same list as {@link #getSearchableAttributes()}.
+ * @deprecated the name is misspelled; use {@link #getSearchableAttributes()}. Not removable yet:
+ * {@code webdynamicform-plugin}'s {@code messageFinding.jsp} still binds to
+ * {@code searcheableAttributes}, and no JSP is compiled by this build, so deleting this would break
+ * that page silently. Retire it together with that binding.
+ */
@Deprecated
public List getSearcheableAttributes() {
return this.getSearchableAttributes();
}
-
+
+ /**
+ * The searchable top-level attributes, as real {@link AttributeInterface} instances.
+ *
+ * This deliberately keeps its historical return type. It is a {@code public} method on a
+ * {@code public abstract} class that downstream projects extend, and its elements are addressed by
+ * custom JSPs through arbitrary attribute properties ({@code #attribute.items},
+ * {@code #attribute.roles}, ...). Narrowing it to a projection would compile cleanly here and then
+ * fail at runtime in customer code - silently in JSPs, which this build never compiles.
+ *
+ * Nested searchable attributes are not included here, because they cannot be represented
+ * as a plain attribute: their form field is named after a path key, not after
+ * {@code attribute.getName()}. Search forms that support them must iterate
+ * {@link #getSearchableAttributeRefs()} instead.
+ *
+ * @return the ordered list of searchable top-level attributes; never null.
+ */
public List getSearchableAttributes() {
- List searchableAttributes = new ArrayList();
+ List searchableAttributes = new ArrayList<>();
IApsEntity prototype = this.getEntityPrototype();
if (null == prototype) {
return searchableAttributes;
}
- List contentAttributes = prototype.getAttributeList();
- for (int i=0; i_}.
+ *
+ * Each entry is a {@link SearchableAttributeRef} - the key, the display label and the real
+ * attribute - not a renamed copy of the attribute. OGNL resolves {@code #attribute.name} to the key,
+ * {@code #attribute.type} and {@code #attribute.textAttribute} to the real attribute's own values;
+ * anything else is reached through {@code #attribute.source.}.
+ *
+ * This is additive: {@link #getSearchableAttributes()} keeps its original contract for
+ * pre-existing callers, and only forms that need nested attributes bind to this one.
+ *
+ * @return the ordered list of searchable attribute references; never null.
+ */
+ public List getSearchableAttributeRefs() {
+ return this.getSearchSchema().getSearchableAttributes();
+ }
+
+ /**
+ * Display labels for {@link #getSearchableAttributeRefs()}, keyed by the attribute's machine key.
+ * A nested boolean's label is its hierarchy (e.g. {@code "compo > cmp_bool"}) reconstructed from the
+ * real attribute tree, so the search form renders it verbatim instead of splitting the flattened key
+ * on '_' - which would mis-segment a name that itself contains '_'.
+ * @return a map from machine key to display label; never null.
+ * @deprecated a form iterating {@link #getSearchableAttributeRefs()} already holds the label:
+ * {@code #attribute.label}. The core finder JSPs no longer bind this. Not removable yet: a downstream
+ * JSP may still bind {@code searchableAttributeLabels}, and no JSP is compiled by this build, so
+ * deleting it would break that page silently.
+ */
+ @Deprecated
+ public Map getSearchableAttributeLabels() {
+ return this.getSearchSchema().getLabels();
+ }
+
+ /**
+ * What the current entity type offers to a search: the attributes, their labels and the key each is
+ * addressed by. Read from the entity manager, which computes it once per type and keeps it, so a
+ * form render no longer walks the attribute tree once per question it asks.
+ * @return the schema of the current type; never null.
+ */
+ protected EntitySearchSchema getSearchSchema() {
+ IEntityManager entityManager = this.getEntityManager();
+ EntitySearchSchema schema = (null == entityManager)
+ ? null : entityManager.getSearchSchema(this.getEntityTypeCode());
+ // a finder without a manager has nothing to offer, and must not throw while a form renders
+ return (null != schema) ? schema
+ : EntitySearchSchema.build(null, EntitySearchKeys.DEFAULT_MAX_KEY_LENGTH);
+ }
+
public List getAttributeRoles() {
return this.getEntityManager().getAttributeRoles();
}
@@ -246,4 +320,4 @@ protected void setFilters(EntitySearchFilter[] filters) {
private EntitySearchFilter[] _filters = new EntitySearchFilter[0];
-}
\ No newline at end of file
+}
diff --git a/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/EntityActionHelper.java b/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/EntityActionHelper.java
index 86f781e2dc..12393d65ff 100644
--- a/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/EntityActionHelper.java
+++ b/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/EntityActionHelper.java
@@ -25,6 +25,8 @@
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
+import org.entando.entando.aps.system.common.entity.search.NestedSearchSupport;
+import org.entando.entando.aps.system.common.entity.search.SearchableAttributeRef;
import com.agiletec.aps.system.common.entity.model.ApsEntity;
import com.agiletec.aps.system.common.entity.model.AttributeFieldError;
import com.agiletec.aps.system.common.entity.model.AttributeTracer;
@@ -33,10 +35,8 @@
import com.agiletec.aps.system.common.entity.model.attribute.AbstractAttribute;
import com.agiletec.aps.system.common.entity.model.attribute.AttributeInterface;
import com.agiletec.aps.system.common.entity.model.attribute.AttributeRole;
-import com.agiletec.aps.system.common.entity.model.attribute.BooleanAttribute;
-import com.agiletec.aps.system.common.entity.model.attribute.DateAttribute;
-import com.agiletec.aps.system.common.entity.model.attribute.ITextAttribute;
-import com.agiletec.aps.system.common.entity.model.attribute.NumberAttribute;
+import com.agiletec.aps.system.common.entity.model.attribute.ThreeStateAttribute;
+import org.entando.entando.aps.system.common.entity.search.SearchFieldType;
import com.agiletec.aps.util.CheckFormatUtil;
import com.agiletec.aps.util.DateConverter;
import com.agiletec.apsadmin.system.BaseActionHelper;
@@ -49,10 +49,15 @@
* classes which handle elements built with the "ApsEntity' entries.
* @author E.Santoboni
*/
+// NOTE: java:S2143 ("use the java.time API") is intentionally suppressed. Legacy java.util.Date is used
+// only to parse the date-range search form fields (via DateConverter); java.time migration is out of
+// scope for ESB-1133 (boolean search) and tracked separately.
+@SuppressWarnings("java:S2143")
public class EntityActionHelper extends BaseActionHelper implements IEntityActionHelper, BeanFactoryAware {
private static final EntLogger _logger = EntLogFactory.getSanitizedLogger(EntityActionHelper.class);
-
+
+
@Override
public void updateEntity(IApsEntity currentEntity, HttpServletRequest request) {
try {
@@ -192,36 +197,53 @@ public EntitySearchFilter[] getAttributeFilters(AbstractApsEntityFinderAction en
if (null == prototype) {
return filters;
}
- List contentAttributes = prototype.getAttributeList();
- for (int i = 0; i < contentAttributes.size(); i++) {
- AttributeInterface attribute = contentAttributes.get(i);
- if (attribute.isActive() && attribute.isSearchable()) {
- if (attribute instanceof ITextAttribute) {
- String insertedText = entityFinderAction.getSearchFormFieldValue(attribute.getName() + "_textFieldName");
- if (null != insertedText && insertedText.trim().length() > 0) {
- EntitySearchFilter filterToAdd = new EntitySearchFilter(attribute.getName(), true, insertedText.trim(), true);
- filters = this.addFilter(filters, filterToAdd);
- }
- } else if (attribute instanceof DateAttribute) {
- Date dateStart = this.getDateSearchFormValue(entityFinderAction, attribute.getName(), "_dateStartFieldName", true);
- Date dateEnd = this.getDateSearchFormValue(entityFinderAction, attribute.getName(), "_dateEndFieldName", false);
- if (null != dateStart || null != dateEnd) {
- EntitySearchFilter filterToAdd = new EntitySearchFilter(attribute.getName(), true, dateStart, dateEnd);
- filters = this.addFilter(filters, filterToAdd);
- }
- } else if (attribute instanceof BooleanAttribute) {
- String booleanValue = entityFinderAction.getSearchFormFieldValue(attribute.getName() + "_booleanFieldName");
- if (null != booleanValue && booleanValue.trim().length() > 0) {
- EntitySearchFilter filterToAdd = new EntitySearchFilter(attribute.getName(), true, booleanValue, false);
- filters = this.addFilter(filters, filterToAdd);
- }
- } else if (attribute instanceof NumberAttribute) {
- BigDecimal numberStart = this.getNumberSearchFormValue(entityFinderAction, attribute.getName(), "_numberStartFieldName", true);
- BigDecimal numberEnd = this.getNumberSearchFormValue(entityFinderAction, attribute.getName(), "_numberEndFieldName", false);
- if (null != numberStart || null != numberEnd) {
- EntitySearchFilter filterToAdd = new EntitySearchFilter(attribute.getName(), true, numberStart, numberEnd);
- filters = this.addFilter(filters, filterToAdd);
- }
+ // Same list the search form is built from: searchable top-level attributes plus Composite-nested
+ // boolean-like attributes keyed by "_". Iterating the identical list
+ // guarantees the parser resolves exactly the field names the form submitted. The eligibility
+ // gate (active/searchable, boolean-like when nested) is applied once, by collectSearchable; the
+ // dispatch below reads the REAL attribute, so the type is always the genuine one.
+ List searchableAttributes = NestedSearchSupport
+ .collectSearchable(prototype);
+ for (SearchableAttributeRef ref : searchableAttributes) {
+ String key = ref.key();
+ // One dispatch mechanism, the same one the finder JSPs use: the attribute's declared search
+ // field type, except for text. TEXT and isTextAttribute() happen to coincide for every type
+ // the platform ships, but they are different questions - TEXT is "indexable as free text",
+ // isTextAttribute() is "carries a per-language text a filter can match" - so the text branch
+ // keeps asking the narrower one it has always asked.
+ if (ref.isTextAttribute()) {
+ String insertedText = entityFinderAction.getSearchFormFieldValue(key + "_textFieldName");
+ if (null != insertedText && insertedText.trim().length() > 0) {
+ EntitySearchFilter filterToAdd = new EntitySearchFilter(key, true, insertedText.trim(), true);
+ filters = this.addFilter(filters, filterToAdd);
+ }
+ } else if (ref.isDate()) {
+ Date dateStart = this.getDateSearchFormValue(entityFinderAction, key, "_dateStartFieldName", true);
+ Date dateEnd = this.getDateSearchFormValue(entityFinderAction, key, "_dateEndFieldName", false);
+ if (null != dateStart || null != dateEnd) {
+ EntitySearchFilter filterToAdd = new EntitySearchFilter(key, true, dateStart, dateEnd);
+ filters = this.addFilter(filters, filterToAdd);
+ }
+ } else if (ref.isTristate()) {
+ // Three states: "true"/"false" filter by value; the "not set" literal matches the unset
+ // state, which on the DB search path is the ABSENCE of a record (a ThreeState writes no
+ // row when unset) - so it is queried via the null option, not a value; blank means "Any".
+ EntitySearchFilter filterToAdd = this.buildThreeStateFilter(entityFinderAction, key);
+ if (null != filterToAdd) {
+ filters = this.addFilter(filters, filterToAdd);
+ }
+ } else if (ref.isBooleanLike()) {
+ String booleanValue = entityFinderAction.getSearchFormFieldValue(key + "_booleanFieldName");
+ if (null != booleanValue && booleanValue.trim().length() > 0) {
+ EntitySearchFilter filterToAdd = new EntitySearchFilter(key, true, booleanValue, false);
+ filters = this.addFilter(filters, filterToAdd);
+ }
+ } else if (ref.isNumber()) {
+ BigDecimal numberStart = this.getNumberSearchFormValue(entityFinderAction, key, "_numberStartFieldName", true);
+ BigDecimal numberEnd = this.getNumberSearchFormValue(entityFinderAction, key, "_numberEndFieldName", false);
+ if (null != numberStart || null != numberEnd) {
+ EntitySearchFilter filterToAdd = new EntitySearchFilter(key, true, numberStart, numberEnd);
+ filters = this.addFilter(filters, filterToAdd);
}
}
}
@@ -231,18 +253,51 @@ public EntitySearchFilter[] getAttributeFilters(AbstractApsEntityFinderAction en
@Override
public String[] getAttributeFilterFieldName(ApsEntity prototype, String attrName) {
AbstractAttribute attr = (AbstractAttribute) prototype.getAttribute(attrName);
+ if (null == attr) {
+ // Not a top-level attribute: it may be a Composite-nested boolean addressed by its
+ // path key "_". Resolve it so the remembered search round-trips.
+ attr = (AbstractAttribute) NestedSearchSupport.resolveNestedByKey(prototype, attrName);
+ }
+ if (null == attr) {
+ return new String[0];
+ }
+ // Same dispatch as getAttributeFilters, so the field names derived here are the ones parsed there.
+ // Null-safe on purpose: a searchable Composite/Monolist declares no search field type.
+ SearchFieldType searchFieldType = attr.getSearchFieldType();
if (attr.isTextAttribute()) {
return new String[] {attrName + "_textFieldName"};
- } else if (attr instanceof DateAttribute) {
+ } else if (SearchFieldType.DATE == searchFieldType) {
return new String[] {attrName + "_dateStartFieldName", attrName + "_dateEndFieldName"};
- } else if (attr instanceof NumberAttribute) {
+ } else if (SearchFieldType.NUMBER == searchFieldType) {
return new String[] {attrName + "_numberStartFieldName", attrName + "_numberEndFieldName"};
- } else if (attr instanceof BooleanAttribute) {
+ } else if (null != searchFieldType && searchFieldType.isBooleanFamily()) {
+ // The whole boolean family shares one form field; ThreeState differs only in the values it
+ // offers, which buildThreeStateFilter handles.
return new String[] {attrName + "_booleanFieldName"};
}
- return null;
+ return new String[0];
}
+ /**
+ * Build the search filter for a ThreeState attribute from its {@code _booleanFieldName} form field.
+ * Blank -> {@code null} ("Any", no filter). {@code "none"} ("Not set") -> a null-option filter,
+ * because an unset ThreeState leaves no DB search record. {@code "true"}/{@code "false"} -> a value
+ * filter, as for a plain boolean.
+ */
+ private EntitySearchFilter buildThreeStateFilter(AbstractApsEntityFinderAction entityFinderAction, String attrName) {
+ String value = entityFinderAction.getSearchFormFieldValue(attrName + "_booleanFieldName");
+ if (null == value || value.trim().isEmpty()) {
+ return null;
+ }
+ value = value.trim();
+ if (ThreeStateAttribute.NOT_SET_SEARCH_VALUE.equalsIgnoreCase(value)) {
+ EntitySearchFilter filter = new EntitySearchFilter(attrName, true);
+ filter.setNullOption(true);
+ return filter;
+ }
+ return new EntitySearchFilter(attrName, true, value, false);
+ }
+
private Date getDateSearchFormValue(AbstractApsEntityFinderAction entityFinderAction,
String fieldName, String dateFieldNameSuffix, boolean start) {
String inputFormName = fieldName + dateFieldNameSuffix;
diff --git a/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/type/AbstractBaseEntityAttributeConfigAction.java b/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/type/AbstractBaseEntityAttributeConfigAction.java
index 2f6c5560bd..d401d43b26 100644
--- a/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/type/AbstractBaseEntityAttributeConfigAction.java
+++ b/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/type/AbstractBaseEntityAttributeConfigAction.java
@@ -14,6 +14,7 @@
package com.agiletec.apsadmin.system.entity.type;
import com.agiletec.aps.system.common.entity.IEntityManager;
+import org.entando.entando.aps.system.common.entity.search.NestedSearchSupport;
import com.agiletec.aps.system.common.entity.model.IApsEntity;
import com.agiletec.aps.system.common.entity.model.attribute.AttributeInterface;
import com.agiletec.aps.system.common.entity.model.attribute.AttributeRole;
@@ -47,6 +48,10 @@
* Base action for Configure Entity Attributes.
* @author E.Santoboni
*/
+// NOTE: java:S2143 ("use the java.time API") is intentionally suppressed. This date-range attribute
+// config action is inherently built on java.util.Date (range start/end/equal fields); migrating it to
+// java.time is out of scope for ESB-1133 (boolean search) and tracked separately.
+@SuppressWarnings("java:S2143")
public class AbstractBaseEntityAttributeConfigAction extends BaseAction implements BeanFactoryAware {
private static final EntLogger _logger = EntLogFactory.getSanitizedLogger(AbstractBaseEntityAttributeConfigAction.class);
@@ -266,6 +271,24 @@ public boolean isSearchableOptionSupported(String attributeTypeCode) {
}
return false;
}
+
+ /**
+ * Whether the given attribute type may be flagged searchable when used as a composite child.
+ * Only boolean-like children (Boolean, CheckBox, ThreeState) are indexed (under the path key
+ * "<composite>_<boolean>") in the DB search tables; every other type is forced
+ * non-searchable as a composite child, so the searchable option must not be offered for them.
+ * @param attributeTypeCode the attribute type code.
+ * @return true only for the boolean-like types.
+ */
+ public boolean isNestedSearchableOptionSupported(String attributeTypeCode) {
+ try {
+ AttributeInterface attribute = this.getAttributePrototype(attributeTypeCode);
+ return NestedSearchSupport.isIndexableNested(attribute);
+ } catch (Exception t) {
+ _logger.error("error in isNestedSearchableOptionSupported", t);
+ }
+ return false;
+ }
public AttributeInterface getAttributePrototype(String typeCode) {
IEntityManager entityManager = this.getEntityManager();
@@ -660,4 +683,4 @@ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
private BeanFactory _beanFactory;
-}
\ No newline at end of file
+}
diff --git a/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/type/CompositeAttributeConfigAction.java b/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/type/CompositeAttributeConfigAction.java
index 59ab858365..35b3a3b7c4 100644
--- a/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/type/CompositeAttributeConfigAction.java
+++ b/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/type/CompositeAttributeConfigAction.java
@@ -120,6 +120,7 @@ public String saveAttributeElement() {
AttributeInterface attribute = this.getAttributePrototype(this.getAttributeTypeCode());
attribute.setName(this.getAttributeName());
super.fillAttributeFields(attribute);
+ this.clearSearchableWithinList(attribute);
composite.getAttributes().add(attribute);
composite.getAttributeMap().put(attribute.getName(), attribute);
}
@@ -155,6 +156,21 @@ public String saveCompositeAttribute() {
return SUCCESS;
}
+ /**
+ * Force the {@code searchable} flag off when the Composite being edited is the nested type of a
+ * List/Monolist. A boolean reached through a list is never indexed as a per-attribute filter by
+ * either search engine, so the flag would be inert; the form does not offer it in that case, but
+ * this also covers a stale or forged submission.
+ * @param attribute the composite child being saved.
+ */
+ private void clearSearchableWithinList(AttributeInterface attribute) {
+ if (null != this.getListAttribute() && attribute.isSearchable()) {
+ _logger.debug("Ignoring the searchable flag on '{}': the composite is nested in the list '{}'",
+ attribute.getName(), this.getListAttribute().getName());
+ attribute.setSearchable(false);
+ }
+ }
+
public List getAllowedAttributeElementTypes() {
List attributes = new ArrayList();
try {
diff --git a/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/type/EntityTypeConfigAction.java b/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/type/EntityTypeConfigAction.java
index 23a079562f..cc6a58a24d 100644
--- a/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/type/EntityTypeConfigAction.java
+++ b/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/type/EntityTypeConfigAction.java
@@ -24,6 +24,8 @@
import com.agiletec.aps.system.common.entity.IEntityManager;
import com.agiletec.aps.system.common.entity.IEntityTypesConfigurer;
+import org.entando.entando.aps.system.common.entity.search.EntitySearchKeys;
+import org.entando.entando.aps.system.common.entity.search.NestedSearchSupport;
import com.agiletec.aps.system.common.entity.model.IApsEntity;
import com.agiletec.aps.system.common.entity.model.attribute.AttributeInterface;
import com.agiletec.apsadmin.system.ApsAdminSystemConstants;
@@ -34,6 +36,9 @@
public class EntityTypeConfigAction extends AbstractEntityConfigAction implements IEntityTypeConfigAction {
private static final EntLogger _logger = EntLogFactory.getSanitizedLogger(EntityTypeConfigAction.class);
+
+ /** Form field the entry page renders every field error against (it lists them in one summary block). */
+ private static final String ENTITY_TYPE_CODE_FIELD = "entityTypeCode";
@Override
public void validate() {
@@ -42,11 +47,53 @@ public void validate() {
if (this.getOperationId() == ApsAdminSystemConstants.ADD && !this.hasFieldErrors()) {
if (null != this.getEntityPrototype(entityType.getTypeCode())) {
String[] args = {entityType.getTypeCode()};
- this.addFieldError("entityTypeCode", this.getText("error.entity.alredy.exists", args));
+ this.addFieldError(ENTITY_TYPE_CODE_FIELD, this.getText("error.entity.alredy.exists", args));
+ }
+ }
+ this.checkNestedSearchKeys(entityType);
+ }
+
+ /**
+ * Report, as field errors, the nested boolean search keys the type would write and that the engine
+ * refuses to persist: keys produced by more than one attribute path, and keys longer than the DB
+ * column that has to store them. Without this the save would fail with a bare stack trace and the
+ * generic failure page; here the author is told which key is wrong and why, and is returned to the
+ * form (the {@code input} result of {@code saveEntityType}).
+ * @param entityType the entity type about to be saved.
+ */
+ private void checkNestedSearchKeys(IApsEntity entityType) {
+ for (NestedSearchSupport.KeyProblem problem : NestedSearchSupport
+ .validateNestedSearchKeys(entityType, this.getMaxSearchKeyLength())) {
+ if (NestedSearchSupport.KeyProblemType.DUPLICATED == problem.type()) {
+ String[] args = {problem.key(), problem.getJoinedPaths()};
+ this.addFieldError(ENTITY_TYPE_CODE_FIELD,
+ this.getText("error.entity.nestedBoolean.key.duplicated", args));
+ } else if (NestedSearchSupport.KeyProblemType.AMBIGUOUS_SEGMENT == problem.type()) {
+ String[] args = {problem.getJoinedPaths(), EntitySearchKeys.KEY_SEPARATOR};
+ this.addFieldError(ENTITY_TYPE_CODE_FIELD,
+ this.getText("error.entity.nestedBoolean.key.ambiguousSegment", args));
+ } else {
+ String[] args = {problem.key(), String.valueOf(problem.key().length()),
+ String.valueOf(problem.maxKeyLength())};
+ this.addFieldError(ENTITY_TYPE_CODE_FIELD,
+ this.getText("error.entity.nestedBoolean.key.tooLong", args));
}
}
}
+ /**
+ * The {@code attrname} width the keys of this type have to fit, declared by the manager the type
+ * belongs to. Falls back to the platform default when there is no manager to ask: {@code validate()}
+ * can run before one has been resolved, and a missing manager must not turn a validation pass into a
+ * NullPointerException.
+ * @return the maximum search key length, in characters.
+ */
+ private int getMaxSearchKeyLength() {
+ IEntityManager entityManager = this.getEntityManager();
+ return (null != entityManager)
+ ? entityManager.getMaxSearchKeyLength() : EntitySearchKeys.DEFAULT_MAX_KEY_LENGTH;
+ }
+
@Override
public String addEntityType() {
try {
@@ -71,7 +118,7 @@ public String editEntityType() {
IApsEntity entityType = this.getEntityPrototype(this.getEntityTypeCode());
if (null == entityType) {
String[] args = {this.getEntityTypeCode()};
- this.addFieldError("entityTypeCode", this.getText("error.entity.type.null",args));
+ this.addFieldError(ENTITY_TYPE_CODE_FIELD, this.getText("error.entity.type.null",args));
return INPUT;
}
this.initSessionParams(entityType, ApsAdminSystemConstants.EDIT);
@@ -300,4 +347,4 @@ public void setAttributeName(String attributeName) {
private String _attributeTypeCode;
private String _attributeName;
-}
\ No newline at end of file
+}
diff --git a/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/type/package_en.properties b/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/type/package_en.properties
index e18467ab98..125877ff35 100644
--- a/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/type/package_en.properties
+++ b/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/type/package_en.properties
@@ -9,6 +9,9 @@ error.entity.type.null=An entity type with code ''{0}'' does not exist
error.entity.null=An entity with code ''{0}'' does not exist
error.entityManager.invalid=The component whose code is ''{0}'' is not valid
error.attribute.not.exists=The attribute ''{0}'' does not exist
+error.entity.nestedBoolean.key.duplicated=The search key ''{0}'' is produced by more than one attribute ({1}): rename one of them so that every searchable attribute has a unique key
+error.entity.nestedBoolean.key.ambiguousSegment=The attribute path ''{0}'' cannot be encoded unambiguously: a name must not begin or end with ''{1}'', because there it cannot be told apart from the separator between two names
+error.entity.nestedBoolean.key.tooLong=The search key ''{0}'' is {1} characters long, exceeding the maximum of {2}: use shorter composite/attribute names
invalid.fieldvalue.minLength=The minimum length is not valid
invalid.fieldvalue.maxLength=The maximum length is not valid
diff --git a/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/type/package_it.properties b/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/type/package_it.properties
index 7d3b7f8eed..2ee0d413d7 100644
--- a/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/type/package_it.properties
+++ b/admin-console/src/main/java/com/agiletec/apsadmin/system/entity/type/package_it.properties
@@ -9,6 +9,9 @@ error.entity.type.null=Il Tipo di Entità con codice ''{0}'' non esiste
error.entity.null=Entità con codice ''{0}'' non esiste
error.entityManager.invalid=Il Componente con codice ''{0}'' non è valido
error.attribute.not.exists=L''Attributo con codice ''{0}'' non esiste
+error.entity.nestedBoolean.key.duplicated=La chiave di ricerca ''{0}'' è prodotta da più di un attributo ({1}): rinomina uno di essi in modo che ogni attributo ricercabile abbia una chiave univoca
+error.entity.nestedBoolean.key.ambiguousSegment=Il percorso di attributi ''{0}'' non può essere codificato in modo univoco: un nome non deve iniziare o terminare con ''{1}'', perché in quella posizione non è distinguibile dal separatore fra due nomi
+error.entity.nestedBoolean.key.tooLong=La chiave di ricerca ''{0}'' è lunga {1} caratteri e supera il massimo di {2}: usa nomi più corti per composito/attributo
invalid.fieldvalue.minLength=Il formato del campo Lunghezza minima non risulta valido
invalid.fieldvalue.maxLength=Il formato del campo Lunghezza massima non risulta valido
diff --git a/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/entity/attribute-type-entry-composite-element.jsp b/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/entity/attribute-type-entry-composite-element.jsp
index cd92c28cd6..63daa0053e 100644
--- a/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/entity/attribute-type-entry-composite-element.jsp
+++ b/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/entity/attribute-type-entry-composite-element.jsp
@@ -135,6 +135,23 @@
+ <%-- The filter option is offered only for boolean-like children of a Composite that is NOT
+ inside a List/Monolist: a boolean reached through a list is never indexed as a filter
+ by any search engine, so offering the flag there would let a user enable something
+ that can never work. --%>
+
+
+
diff --git a/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/entity/attribute-type-entry-composite.jsp b/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/entity/attribute-type-entry-composite.jsp
index a9e76a342b..5b3d52e5c7 100644
--- a/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/entity/attribute-type-entry-composite.jsp
+++ b/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/entity/attribute-type-entry-composite.jsp
@@ -96,6 +96,7 @@
|
|
|
+ |
|
@@ -103,14 +104,20 @@
|
|
-
-
- ">
-
-
- ">
-
- |
+
+
+
+
+
+ <%-- The searchable flag survives only on boolean-like Composite children
+ (CompositeAttribute.extractAttributeCompositeElement forces every other
+ type non-searchable), and a Composite reached through a list is never
+ indexed per attribute: both cases render as "not applicable". --%>
+
+
+
+
+
|
-
- ">
-
-
- ">
-
- |
-
- ">
-
-
- ">
-
- |
-
-
-
-
-
- |
-
-
+ <%-- Complex containers (Composite, List, Monolist) are never searchable
+ themselves - only their children can be - so they render as "not applicable"
+ rather than as an unchecked box. --%>
+
+
+
+
+
-
-
-
+
+
+
+
+
+ |
+
+
+
+
+
+
diff --git a/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/entity/modules/include/threeStateAttributeInputField.jsp b/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/entity/modules/include/threeStateAttributeInputField.jsp
index d95d03e64c..d8e9fffd24 100644
--- a/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/entity/modules/include/threeStateAttributeInputField.jsp
+++ b/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/entity/modules/include/threeStateAttributeInputField.jsp
@@ -24,6 +24,6 @@
id="none_%{#currentThreestateAttributeNameVar}"
value=""
checked="%{#attribute.booleanValue == null}"/>
-
+
\ No newline at end of file
diff --git a/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/user/user-list.jsp b/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/user/user-list.jsp
index 8c391c3110..1da1041c40 100644
--- a/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/user/user-list.jsp
+++ b/admin-console/src/main/webapp/WEB-INF/apsadmin/jsp/user/user-list.jsp
@@ -76,7 +76,7 @@
-
+
@@ -88,16 +88,19 @@
@@ -114,7 +117,10 @@
-
+ <%-- Keep the panel open across the full-page reload triggered by "Set"
+ (changeProfileType) and by a search: the profile-type selector and its
+ attribute filters live inside it. --%>
+
in">
- <%-- Boolean & ThreeState --%>
+ <%-- ThreeState: Any, Yes, No, Not set (the unset/none state) --%>
+
+
+
+
+
+ <%-- Two-valued boolean family: Any, Yes, No --%>
+ test="#attribute.booleanLike && !#attribute.tristate">