Grails 7.0.0-M1 - difficulties applying default constraints in application.groovy
Expected Behavior
No response
Actual Behaviour
Not completely sure of precise cause atm, but behaviour i was seeing was that application.groovy was not being merged into PropertySources and default constraints were not being applied.
Works if you bootRun
Also - related, but separate, if I include CXF's starter-jaxws as an implementation dependency, then, boitRun also fails - it seems the auto wiring of grailsapplication does not occur in DefaultContraintEvaluatorFactory so it doesn't wire in the default constraints. If I take that starter out, and just use CXF classes the grailApplication is correctly injected
I'll try to find time to produce a minimal criminal, and attach
In the meantime, I worked around the application.groovy issue by creating a listener similar to that used in external-config library to manually load appliccation.groovy, slurp it and create a PropertySource which I add to the end of the list
Steps To Reproduce
Built a web based Grails app with a domain class
Have a application.yml and application.groovy in grails-app/conf
In application.groovy have a grails.gorm.default.constraints closure
Run in Tomcat with an external application.properties with the its location specified as a Spring.config.location
Environment Information
No response
Example Application
No response
Version
7.0.0-M1
Attached is a bad app in a zip file. You need Make and docker installed to follow the steps below.
Unzip and in root directory run make docker-image to build a war and then put it in a docker image that runs tomcat
You can run the image with make docker-run
Once its started if you go to http://localhost:8080/badapp/test/index then you will see 0 test records
If you then go to http://localhost:8080/badapp/test/save it should record 2 test records being saved, but in tomcat it only records 1.
The reason is that the application.groovy in the war is not loaded when the app starts, so the default constraints included in that file do not get added to the domain classes. One of the test record saves has a null property, so that save fails.
This works if you run using bootRun. The application.groovy is added to the PropertySources, and so the default constraints are applied.
Finally - the build.gradle contains a commented out ref to implementation "org.apache.cxf:cxf-spring-boot-starter-jaxws:4.1.0"
If that is uncommented - then the default constraints are not applied. If you debug you see that DefaultConstraintEvaluatorFactoryBean.getObject is called but with that cxf dependency applied, the grailApplication object is null so the default constraint config is not available to be applied
Thank you @boardbloke for reporting this. We've added it to the next milestone.
For reference, here's the gradle dependency graph of the library that causes the problem:
\--- org.apache.cxf:cxf-spring-boot-starter-jaxws:4.1.0
+--- org.springframework.boot:spring-boot-starter:3.4.0 -> 3.4.1 (*)
+--- org.springframework.boot:spring-boot-starter-web:3.4.0 -> 3.4.1 (*)
+--- org.apache.cxf:cxf-spring-boot-autoconfigure:4.1.0
| \--- org.springframework.boot:spring-boot-autoconfigure:3.4.0 -> 3.4.1 (*)
+--- org.apache.cxf:cxf-rt-transports-http:4.1.0
| \--- org.apache.cxf:cxf-core:4.1.0
| +--- jakarta.annotation:jakarta.annotation-api:2.1.1
| +--- org.glassfish.jaxb:jaxb-runtime:4.0.5 (*)
| +--- com.fasterxml.woodstox:woodstox-core:7.1.0
| | \--- org.codehaus.woodstox:stax2-api:4.2.2
| +--- org.apache.ws.xmlschema:xmlschema-core:2.3.1
| +--- org.eclipse.angus:angus-activation:2.0.2
| | \--- jakarta.activation:jakarta.activation-api:2.1.3
| \--- jakarta.xml.bind:jakarta.xml.bind-api:4.0.2 (*)
+--- org.apache.cxf:cxf-rt-frontend-jaxws:4.1.0
| +--- xml-resolver:xml-resolver:1.2
| +--- org.ow2.asm:asm:9.7.1
| +--- org.apache.cxf:cxf-core:4.1.0 (*)
| +--- org.apache.cxf:cxf-rt-bindings-soap:4.1.0
| | +--- jakarta.xml.soap:jakarta.xml.soap-api:3.0.2
| | | \--- jakarta.activation:jakarta.activation-api:2.1.3
| | +--- jakarta.jws:jakarta.jws-api:3.0.0
| | +--- jakarta.xml.ws:jakarta.xml.ws-api:4.0.2
| | | +--- jakarta.xml.bind:jakarta.xml.bind-api:4.0.2 (*)
| | | \--- jakarta.xml.soap:jakarta.xml.soap-api:3.0.2 (*)
| | +--- org.apache.cxf:cxf-core:4.1.0 (*)
| | +--- org.apache.cxf:cxf-rt-wsdl:4.1.0
| | | +--- org.apache.cxf:cxf-core:4.1.0 (*)
| | | +--- wsdl4j:wsdl4j:1.6.3
| | | \--- org.ow2.asm:asm:9.7.1
| | +--- org.apache.cxf:cxf-rt-databinding-jaxb:4.1.0
| | | +--- org.apache.cxf:cxf-core:4.1.0 (*)
| | | \--- org.apache.cxf:cxf-rt-wsdl:4.1.0 (*)
| | \--- org.eclipse.angus:angus-activation:2.0.2 (*)
| +--- org.apache.cxf:cxf-rt-bindings-xml:4.1.0
| | \--- org.apache.cxf:cxf-core:4.1.0 (*)
| +--- org.apache.cxf:cxf-rt-frontend-simple:4.1.0
| | +--- org.apache.cxf:cxf-core:4.1.0 (*)
| | +--- org.apache.cxf:cxf-rt-bindings-soap:4.1.0 (*)
| | +--- org.apache.cxf:cxf-rt-wsdl:4.1.0 (*)
| | \--- org.eclipse.angus:angus-mail:2.0.3
| | +--- jakarta.activation:jakarta.activation-api:2.1.3
| | \--- jakarta.mail:jakarta.mail-api:2.1.3
| | \--- jakarta.activation:jakarta.activation-api:2.1.3
| +--- org.apache.cxf:cxf-rt-ws-addr:4.1.0
| | +--- org.apache.cxf:cxf-core:4.1.0 (*)
| | +--- org.apache.cxf:cxf-rt-bindings-soap:4.1.0 (*)
| | \--- org.apache.cxf:cxf-rt-ws-policy:4.1.0
| | +--- wsdl4j:wsdl4j:1.6.3
| | +--- org.apache.cxf:cxf-core:4.1.0 (*)
| | \--- org.apache.neethi:neethi:3.2.1
| \--- org.eclipse.angus:angus-activation:2.0.2 (*)
+--- org.apache.cxf:cxf-rt-features-metrics:4.1.0
| +--- jakarta.xml.ws:jakarta.xml.ws-api:4.0.2 (*)
| \--- org.apache.cxf:cxf-core:4.1.0 (*)
+--- jakarta.validation:jakarta.validation-api:3.0.2
\--- org.springframework.boot:spring-boot-starter-validation:3.4.0 -> 3.4.1 (*)
Hi @boardbloke ,
I took your example application, and updated it for Grails 7 (latest snapshot). I then published it here: https://github.com/jdaugherty/grails-issue-14035
When I uncomment the CXF dependency, it works locally. Can you please confirm that this bad behavior was only noticeable when run inside of the container if uncommented? Did it always work in bootRun 100% of the time?
Please note: we've upgraded Spring since this was originally reported and are now on a later version than CXF so that may be why it's working now.
-James
@boardbloke Is this still occurring with 7.0.0-RC1?
Hi @boardbloke ,
I took your example application, and updated it for Grails 7 (latest snapshot). I then published it here: https://github.com/jdaugherty/grails-issue-14035
When I uncomment the CXF dependency, it works locally. Can you please confirm that this bad behavior was only noticeable when run inside of the container if uncommented? Did it always work in bootRun 100% of the time?
Please note: we've upgraded Spring since this was originally reported and are now on a later version than CXF so that may be why it's working now.
-James
Hi @jdaugherty , @jamesfredley - yes that was the behavior - worked via bootRun - didn't work when run as a war in a container.
I've submitted a PR to https://github.com/jdaugherty/grails-issue-14035 to upgrade the bad app to 7.0.0-RC1 - (https://github.com/jdaugherty/grails-issue-14035/pull/1) and its still occurring.
This time around though, inclusion of the CXF library makes no difference to boorWar's behaviour, so I've deleted that.
In latest bad app, bootRun works as expected and war file via Tomcat in a container does not.
Steps to reproduce for the latest bad app are the same as above make docker-build followed by make docker-run
Thank you!
@boardbloke My first thought was this is something docker is doing to the war contents based on what we saw on https://github.com/wondrify/asset-pipeline/issues/373 and after removing docker from the equation, which I always take as a first step, I think docker is the difference. Please take a look at /WEB-INF/classes/ and see if application.groovy and application.yml are present after running docker build -t badapp:0.2-SNAPSHOT ./docker
locally I tested this with bootRun and with bootWar + java -jar badapp-0.2.war after changing the following in application.yml
production:
dataSource:
dbCreate: create-drop
url: jdbc:h2:mem:prodDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
Both save two Example instances with application.groovy as is and both only save one Example instance with application.groovy commented out. That confirms that application.groovy appears to be the variable, but given this works with bootRun and with bootWar + java -jar badapp-0.2.war, see what docker is doing as a first step.
Hi @jamesfredley - found it. Operator error!
In the tomcat config is was pulling in an external file via spring.config.location when I should be using spring.config.additional-location. The former replaces default property sources (so application.groovy was not loaded)
https://docs.spring.io/spring-boot/reference/features/external-config.html "Locations configured by using spring.config.location replace the default locations."
Using the proper spring property fixed it.
Forehead slap!
Thanks very much for the support - glad its me and nothing more serious!
This issue can be closed as not a bug! :-)
@boardbloke All good, glad you found it and it is resolved.