codeconsole commented on code in PR #16094:
URL: https://github.com/apache/grails-core/pull/16094#discussion_r3792529178
##########
grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/core/GrailsGradlePlugin.groovy:
##########
@@ -1115,6 +1265,146 @@ ${importStatements}
}
}
+ /**
+ * Wires up the cache the JDK can write for an application, so the next
start reads what a
+ * training run worked out rather than working it out again.
+ *
+ * <p>Three steps, because the cache is only usable against the layout it
was trained on: the
+ * archive is extracted, the extracted application is run and asked for
its pages, and what the
+ * run recorded is left beside it. An application asks for this with
+ * {@code grails.aotCache.enabled}, and says which of its pages matter.</p>
+ */
+ protected void configureAotCache(Project project) {
+ AotCacheExtension extension = ((ExtensionAware)
project.extensions.getByName('grails'))
+ .extensions.create('aotCache', AotCacheExtension)
+ extension.enabled.convention(false)
+ extension.paths.convention([])
+ extension.jvmArguments.convention(['-Dspring.aot.enabled=true',
'-Dgrails.env=production'])
+ extension.port.convention(TRAINING_PORT)
+
extension.startTimeoutSeconds.convention(TRAINING_START_TIMEOUT_SECONDS)
+
+ project.pluginManager.withPlugin(SPRING_BOOT_PLUGIN) {
+ TaskProvider<?> bootJar = project.tasks.named('bootJar')
+ Provider<Directory> application =
project.layout.buildDirectory.dir('aot-cache/application')
+ Provider<JavaLauncher> launcher = trainingLauncher(project)
+
+ TaskProvider<ExtractApplicationTask> extract =
project.tasks.register(
+ 'extractAotCacheApplication', ExtractApplicationTask) {
ExtractApplicationTask task ->
+ task.group = BasePlugin.BUILD_GROUP
+ task.description = 'Extracts the application, which is the
form the cache is read against'
+ task.onlyIf { extension.enabled.get() }
+ task.archiveFile.set(archiveFileOf(bootJar))
+ // Mapped rather than read, so the JDK the toolchain resolves
to is not provisioned
+ // by every build that merely reads this project.
+ task.javaExecutable.set(launcher.map { JavaLauncher java ->
java.executablePath.asFile.absolutePath })
+ task.destination.set(application)
+ }
+
+ project.tasks.register('trainAotCache', TrainAotCacheTask) {
TrainAotCacheTask task ->
+ task.group = BasePlugin.BUILD_GROUP
+ task.description = 'Runs the application once so the JDK can
write down what starting it needs'
+ task.onlyIf { extension.enabled.get() }
+ task.dependsOn(extract)
+ task.applicationDirectory.set(application)
+ task.archiveFileName.set(archiveFileOf(bootJar).map {
RegularFile file -> file.asFile.name })
+ // Beside the extracted application rather than inside it.
Inside, the cache and its
+ // metadata land in the directory this task declares as its
input, so writing them
+ // changes that input and the task can never be up to date --
every build would run
+ // the application again to record what the last one already
recorded.
+ Provider<Directory> beside =
project.layout.buildDirectory.dir('aot-cache')
+ task.cacheFile.set(beside.map { Directory dir ->
dir.file("${project.name}.aot") })
+ task.metadataFile.set(beside.map { Directory dir ->
dir.file('aot-cache.properties') })
+ task.javaExecutable.set(launcher.map { JavaLauncher java ->
java.executablePath.asFile.absolutePath })
+ // Recorded from the JDK that will run the training, not the
one running the build.
+ // A cache is read only by the JDK build that wrote it, and
this is what a deployment
+ // checks that against -- so naming the wrong one is worse
than naming none.
+ task.javaVersion.set(launcher.map { JavaLauncher java ->
java.metadata.jvmVersion })
+ task.javaVendor.set(launcher.map { JavaLauncher java ->
java.metadata.vendor })
+ task.jvmArguments.set(extension.jvmArguments)
+ task.paths.set(extension.paths)
+ task.port.set(extension.port)
+ task.startTimeoutSeconds.set(extension.startTimeoutSeconds)
+ }
+ }
+ }
+
+ /**
+ * The archive a task should consume, as something to be resolved when it
runs.
+ *
+ * <p>A provider rather than a file: asking the task for its archive
resolves the task, and a
+ * task resolved while the build is configured is one every build pays for
whether or not it
+ * asked for an archive.</p>
+ */
+ private static Provider<RegularFile> archiveFileOf(TaskProvider<?>
bootJar) {
+ bootJar.flatMap { Task task -> (Provider<RegularFile>)
task.property('archiveFile') }
+ }
+
+ /**
+ * The JDK the cache will be trained on, which is the project's toolchain
where it declares one.
+ *
+ * <p>A cache is read only by the JDK build that wrote it, so training has
to happen on the JDK
+ * the application is compiled for rather than whichever one happens to be
running Gradle. A
+ * project on the Java 21 baseline with a Java 25 toolchain compiles with
25 and would otherwise
+ * have trained with 21, where the cache options do not exist -- and the
failure it reported was
+ * that the training run ended before it started serving.</p>
+ *
+ * <p>Where no toolchain is declared this resolves to the JDK running the
build, which is also
+ * the one that compiled the application.</p>
+ */
+ private static Provider<JavaLauncher> trainingLauncher(Project project) {
+ JavaToolchainService toolchains =
project.extensions.getByType(JavaToolchainService)
+ JavaPluginExtension java =
project.extensions.getByType(JavaPluginExtension)
+ toolchains.launcherFor(java.toolchain)
+ }
+
+ /**
+ * Records the application's own classes and pages so a native image keeps
them usable. The build
+ * output already names both, so an application does not have to be traced
to be buildable.
+ */
+ protected void configureNativeMetadata(Project project) {
+ // Only where an image is actually being built. The metadata is read
by nothing else, and
+ // generating it means reading the compiled classes and the pages
compiled into every
+ // dependency -- so wiring it into processResources unconditionally
made a build that only
+ // wanted to write a resource compile its sources and resolve its
whole runtime classpath
+ // first, which a project that had declared no repositories could not
do.
+ project.pluginManager.withPlugin(NATIVE_IMAGE_PLUGIN) {
+ configureNativeMetadataTask(project)
+ }
+ }
+
+ private void configureNativeMetadataTask(Project project) {
+ SourceSet sourceSet = SourceSets.findMainSourceSet(project)
+
+ TaskProvider<GenerateNativeMetadataTask> metadataTask =
project.tasks.register(
+ 'generateNativeMetadata', GenerateNativeMetadataTask) {
GenerateNativeMetadataTask task ->
+ task.group = BasePlugin.BUILD_GROUP
+ task.description = 'Records the application classes and pages a
native image must keep'
+ // The classes directory is written by more than one task, so the
dependency has to be
+ // stated. It is stated against the compilation rather than the
classes task, because
+ // the classes task also runs processResources, which consumes
this task's output.
+ task.dependsOn(project.tasks.named(sourceSet.compileJavaTaskName))
+ ['compileGroovy', 'copyAstClasses'].each { String name ->
+ if (project.tasks.findByName(name)) {
+ task.dependsOn(project.tasks.named(name))
+ }
+ }
+ task.classesDirs.from(sourceSet.output.classesDirs)
+
task.pageClassesDirs.from(project.layout.buildDirectory.dir('gsp-classes/main'))
Review Comment:
`709989ae37`. Removed `processResources.from(metadataTask)`. The task now
depends on `compileGroovyPages` via `pluginManager.withPlugin` on the GSP
plugin id, and its output is consumed by `bootJar` into `BOOT-INF/classes` —
where `GroovyPagePlugin` already puts the compiled pages — and by the native
binary classpath via `graalvmNative.binaries.configureEach { classpath(...) }`.
Source-set output does not work: `classes` builds those, which restores the
cycle from the other side.
Added a `native-metadata-ordering` fixture and two tests in
`GrailsNativeImageDefaultsSpec`: `compileGroovyPages` precedes
`generateNativeMetadata` in the `bootJar` graph, and `processResources` no
longer depends on the task while `bootJar` does. Both fail against the previous
wiring.
##########
grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/core/GrailsGradlePlugin.groovy:
##########
@@ -1115,6 +1265,146 @@ ${importStatements}
}
}
+ /**
+ * Wires up the cache the JDK can write for an application, so the next
start reads what a
+ * training run worked out rather than working it out again.
+ *
+ * <p>Three steps, because the cache is only usable against the layout it
was trained on: the
+ * archive is extracted, the extracted application is run and asked for
its pages, and what the
+ * run recorded is left beside it. An application asks for this with
+ * {@code grails.aotCache.enabled}, and says which of its pages matter.</p>
+ */
+ protected void configureAotCache(Project project) {
+ AotCacheExtension extension = ((ExtensionAware)
project.extensions.getByName('grails'))
+ .extensions.create('aotCache', AotCacheExtension)
+ extension.enabled.convention(false)
+ extension.paths.convention([])
+ extension.jvmArguments.convention(['-Dspring.aot.enabled=true',
'-Dgrails.env=production'])
+ extension.port.convention(TRAINING_PORT)
+
extension.startTimeoutSeconds.convention(TRAINING_START_TIMEOUT_SECONDS)
+
+ project.pluginManager.withPlugin(SPRING_BOOT_PLUGIN) {
+ TaskProvider<?> bootJar = project.tasks.named('bootJar')
+ Provider<Directory> application =
project.layout.buildDirectory.dir('aot-cache/application')
+ Provider<JavaLauncher> launcher = trainingLauncher(project)
+
+ TaskProvider<ExtractApplicationTask> extract =
project.tasks.register(
+ 'extractAotCacheApplication', ExtractApplicationTask) {
ExtractApplicationTask task ->
+ task.group = BasePlugin.BUILD_GROUP
+ task.description = 'Extracts the application, which is the
form the cache is read against'
+ task.onlyIf { extension.enabled.get() }
+ task.archiveFile.set(archiveFileOf(bootJar))
+ // Mapped rather than read, so the JDK the toolchain resolves
to is not provisioned
+ // by every build that merely reads this project.
+ task.javaExecutable.set(launcher.map { JavaLauncher java ->
java.executablePath.asFile.absolutePath })
+ task.destination.set(application)
+ }
+
+ project.tasks.register('trainAotCache', TrainAotCacheTask) {
TrainAotCacheTask task ->
+ task.group = BasePlugin.BUILD_GROUP
+ task.description = 'Runs the application once so the JDK can
write down what starting it needs'
+ task.onlyIf { extension.enabled.get() }
+ task.dependsOn(extract)
+ task.applicationDirectory.set(application)
+ task.archiveFileName.set(archiveFileOf(bootJar).map {
RegularFile file -> file.asFile.name })
+ // Beside the extracted application rather than inside it.
Inside, the cache and its
+ // metadata land in the directory this task declares as its
input, so writing them
+ // changes that input and the task can never be up to date --
every build would run
+ // the application again to record what the last one already
recorded.
+ Provider<Directory> beside =
project.layout.buildDirectory.dir('aot-cache')
+ task.cacheFile.set(beside.map { Directory dir ->
dir.file("${project.name}.aot") })
Review Comment:
`0021e820a5`. `String cacheName = project.name` hoisted out of the
`beside.map { }` closure.
##########
grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/core/GrailsGradlePlugin.groovy:
##########
@@ -1115,6 +1265,146 @@ ${importStatements}
}
}
+ /**
+ * Wires up the cache the JDK can write for an application, so the next
start reads what a
+ * training run worked out rather than working it out again.
+ *
+ * <p>Three steps, because the cache is only usable against the layout it
was trained on: the
+ * archive is extracted, the extracted application is run and asked for
its pages, and what the
+ * run recorded is left beside it. An application asks for this with
+ * {@code grails.aotCache.enabled}, and says which of its pages matter.</p>
+ */
+ protected void configureAotCache(Project project) {
+ AotCacheExtension extension = ((ExtensionAware)
project.extensions.getByName('grails'))
+ .extensions.create('aotCache', AotCacheExtension)
+ extension.enabled.convention(false)
+ extension.paths.convention([])
+ extension.jvmArguments.convention(['-Dspring.aot.enabled=true',
'-Dgrails.env=production'])
+ extension.port.convention(TRAINING_PORT)
+
extension.startTimeoutSeconds.convention(TRAINING_START_TIMEOUT_SECONDS)
+
+ project.pluginManager.withPlugin(SPRING_BOOT_PLUGIN) {
+ TaskProvider<?> bootJar = project.tasks.named('bootJar')
+ Provider<Directory> application =
project.layout.buildDirectory.dir('aot-cache/application')
+ Provider<JavaLauncher> launcher = trainingLauncher(project)
+
+ TaskProvider<ExtractApplicationTask> extract =
project.tasks.register(
+ 'extractAotCacheApplication', ExtractApplicationTask) {
ExtractApplicationTask task ->
+ task.group = BasePlugin.BUILD_GROUP
+ task.description = 'Extracts the application, which is the
form the cache is read against'
+ task.onlyIf { extension.enabled.get() }
+ task.archiveFile.set(archiveFileOf(bootJar))
+ // Mapped rather than read, so the JDK the toolchain resolves
to is not provisioned
+ // by every build that merely reads this project.
+ task.javaExecutable.set(launcher.map { JavaLauncher java ->
java.executablePath.asFile.absolutePath })
+ task.destination.set(application)
+ }
+
+ project.tasks.register('trainAotCache', TrainAotCacheTask) {
TrainAotCacheTask task ->
+ task.group = BasePlugin.BUILD_GROUP
+ task.description = 'Runs the application once so the JDK can
write down what starting it needs'
+ task.onlyIf { extension.enabled.get() }
+ task.dependsOn(extract)
+ task.applicationDirectory.set(application)
+ task.archiveFileName.set(archiveFileOf(bootJar).map {
RegularFile file -> file.asFile.name })
+ // Beside the extracted application rather than inside it.
Inside, the cache and its
+ // metadata land in the directory this task declares as its
input, so writing them
+ // changes that input and the task can never be up to date --
every build would run
+ // the application again to record what the last one already
recorded.
+ Provider<Directory> beside =
project.layout.buildDirectory.dir('aot-cache')
+ task.cacheFile.set(beside.map { Directory dir ->
dir.file("${project.name}.aot") })
+ task.metadataFile.set(beside.map { Directory dir ->
dir.file('aot-cache.properties') })
+ task.javaExecutable.set(launcher.map { JavaLauncher java ->
java.executablePath.asFile.absolutePath })
+ // Recorded from the JDK that will run the training, not the
one running the build.
+ // A cache is read only by the JDK build that wrote it, and
this is what a deployment
+ // checks that against -- so naming the wrong one is worse
than naming none.
+ task.javaVersion.set(launcher.map { JavaLauncher java ->
java.metadata.jvmVersion })
+ task.javaVendor.set(launcher.map { JavaLauncher java ->
java.metadata.vendor })
+ task.jvmArguments.set(extension.jvmArguments)
+ task.paths.set(extension.paths)
+ task.port.set(extension.port)
+ task.startTimeoutSeconds.set(extension.startTimeoutSeconds)
+ }
+ }
+ }
+
+ /**
+ * The archive a task should consume, as something to be resolved when it
runs.
+ *
+ * <p>A provider rather than a file: asking the task for its archive
resolves the task, and a
+ * task resolved while the build is configured is one every build pays for
whether or not it
+ * asked for an archive.</p>
+ */
+ private static Provider<RegularFile> archiveFileOf(TaskProvider<?>
bootJar) {
+ bootJar.flatMap { Task task -> (Provider<RegularFile>)
task.property('archiveFile') }
+ }
+
+ /**
+ * The JDK the cache will be trained on, which is the project's toolchain
where it declares one.
+ *
+ * <p>A cache is read only by the JDK build that wrote it, so training has
to happen on the JDK
+ * the application is compiled for rather than whichever one happens to be
running Gradle. A
+ * project on the Java 21 baseline with a Java 25 toolchain compiles with
25 and would otherwise
+ * have trained with 21, where the cache options do not exist -- and the
failure it reported was
+ * that the training run ended before it started serving.</p>
+ *
+ * <p>Where no toolchain is declared this resolves to the JDK running the
build, which is also
+ * the one that compiled the application.</p>
+ */
+ private static Provider<JavaLauncher> trainingLauncher(Project project) {
+ JavaToolchainService toolchains =
project.extensions.getByType(JavaToolchainService)
+ JavaPluginExtension java =
project.extensions.getByType(JavaPluginExtension)
+ toolchains.launcherFor(java.toolchain)
+ }
+
+ /**
+ * Records the application's own classes and pages so a native image keeps
them usable. The build
+ * output already names both, so an application does not have to be traced
to be buildable.
+ */
+ protected void configureNativeMetadata(Project project) {
+ // Only where an image is actually being built. The metadata is read
by nothing else, and
+ // generating it means reading the compiled classes and the pages
compiled into every
+ // dependency -- so wiring it into processResources unconditionally
made a build that only
+ // wanted to write a resource compile its sources and resolve its
whole runtime classpath
+ // first, which a project that had declared no repositories could not
do.
+ project.pluginManager.withPlugin(NATIVE_IMAGE_PLUGIN) {
+ configureNativeMetadataTask(project)
+ }
+ }
+
+ private void configureNativeMetadataTask(Project project) {
+ SourceSet sourceSet = SourceSets.findMainSourceSet(project)
+
+ TaskProvider<GenerateNativeMetadataTask> metadataTask =
project.tasks.register(
+ 'generateNativeMetadata', GenerateNativeMetadataTask) {
GenerateNativeMetadataTask task ->
+ task.group = BasePlugin.BUILD_GROUP
+ task.description = 'Records the application classes and pages a
native image must keep'
+ // The classes directory is written by more than one task, so the
dependency has to be
+ // stated. It is stated against the compilation rather than the
classes task, because
+ // the classes task also runs processResources, which consumes
this task's output.
+ task.dependsOn(project.tasks.named(sourceSet.compileJavaTaskName))
+ ['compileGroovy', 'copyAstClasses'].each { String name ->
+ if (project.tasks.findByName(name)) {
Review Comment:
`0021e820a5`. `project.tasks.names.contains(name)`.
##########
grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/core/GrailsGradlePlugin.groovy:
##########
@@ -836,6 +869,36 @@ ${importStatements}
it.destinationDirectory =
project.layout.buildDirectory.dir('assetCompile/assets')
}
}
+ configureAssetsOnTheClasspath(project)
+ }
+
+ /**
+ * Packages the compiled assets where an executable jar can read them.
+ *
+ * <p>The asset pipeline plugin puts them at the root of whatever archive
is built, which is
+ * where a war serves its web content from and is therefore right for a
war. An executable jar
+ * has no web content: it serves assets by reading them off the classpath,
and its classpath is
+ * {@code BOOT-INF/classes} -- so the same assets, at the same place, in a
jar rather than a war,
+ * are packaged but unreachable, and every asset a page asks for is a 404
while the page itself
+ * renders. Adding them under the classpath directory is what makes them
found.</p>
+ *
+ * <p>Only for {@code bootJar}. A war already serves them from the root,
and putting them on its
+ * classpath as well would ship the same bytes twice.</p>
+ */
+ private void configureAssetsOnTheClasspath(Project project) {
+ project.pluginManager.withPlugin(SPRING_BOOT_PLUGIN) {
+ // Matched by the task the pipeline registers rather than by the
plugin that registers
+ // it: the asset pipeline's plugin id has changed once already,
and the task name has
+ // not. Matched rather than looked up, so nothing depends on which
plugin was applied
+ // first and no task is resolved to find out.
+ FileCollection compiledAssets = project.files(
+ project.tasks.matching { Task task -> task.name ==
ASSET_COMPILE_TASK })
Review Comment:
`e68da7d3db`. Used the `project.provider { }` over `tasks.names` option. The
container is no longer in `bootJar`'s input files, and resolving them realizes
only `assetCompile` rather than running the predicate against every registered
task.
Not the plugin-id route: `AssetClasspathPackagingSpec` registers a stand-in
`assetCompile` because the asset-pipeline plugin is not on the test classpath,
so keying off the id would leave that wiring unexercised. `project` is still
reachable from the closure.
##########
grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/core/GrailsGradlePlugin.groovy:
##########
@@ -83,6 +93,22 @@ import javax.inject.Inject
@CompileStatic
class GrailsGradlePlugin implements Plugin<Project> {
+ private static final String NATIVE_IMAGE_PLUGIN =
'org.graalvm.buildtools.native'
+
+ private static final String SPRING_BOOT_PLUGIN = 'org.springframework.boot'
+
+ private static final String ASSET_COMPILE_TASK = 'assetCompile'
+
+ /** Where an executable jar reads its classpath from, and so where assets
have to be to be found. */
+ private static final String CLASSPATH_ASSETS_PATH =
'BOOT-INF/classes/assets'
+
+ private static final int TRAINING_PORT = 18080
Review Comment:
`3890b3466e`. Added `RunPort.refuseWhereTaken(port, what, setting)`, which
binds the port before the run starts and fails with the port and the setting
that names it. Called from `TrainAotCacheTask` (`grails.aotCache.port`) and
`TraceNativeMetadataTask` (`grails.nativeMetadata.port`). Added `RunPortSpec`.
Bind-check rather than `--server.port=0`, which would remove a documented
setting. It does not close the race between check and launch.
##########
grails-data-neo4j/grails-plugin/src/main/groovy/grails/neo4j/bootstrap/Neo4jDataStoreSpringInitializer.groovy:
##########
@@ -75,17 +74,27 @@ class Neo4jDataStoreSpringInitializer extends
AbstractDatastoreInitializer {
callable.delegate = delegate
callable.call()
- ApplicationEventPublisher eventPublisher
- if (beanDefinitionRegistry instanceof
ConfigurableApplicationContext) {
- eventPublisher = new
ConfigurableApplicationContextEventPublisher((ConfigurableApplicationContext)
beanDefinitionRegistry)
- } else {
- eventPublisher = new DefaultApplicationEventPublisher()
- }
+ // Registered rather than built here, so what the datastore holds
is a reference the
+ // container can build. Holding the publisher itself puts a live
object in the
+ // definition, and generating code for a definition means writing
out what it holds --
+ // which a publisher bound to a running context is not.
+ //
+ // The choice is made here rather than through
AbstractDatastoreInitializer, because this
+ // build resolves that class from a released
grails-datamapping-core rather than from
+ // the source beside it, and a method added there is not one this
can call.
+ grailsDatastoreEventPublisher(beanDefinitionRegistry instanceof
ConfigurableApplicationContext
+ ? ConfigurableApplicationContextEventPublisher
+ : DefaultApplicationEventPublisher)
final boolean isRecentGrailsVersion =
GrailsVersion.isAtLeastMajorMinor(3, 3)
neo4jConnectionSourceFactory(Neo4jConnectionSourceFactory) { bean
->
bean.autowire = true
}
- neo4jDatastore(Neo4jDatastore, configuration,
ref("neo4jConnectionSourceFactory"), eventPublisher,
collectMappedClasses(DATASTORE_TYPE))
+ // The configuration is held rather than named. Naming the
environment instead is what
+ // lets a definition be generated without writing the build
machine's own settings into
+ // it, but that is asked through a method added to
AbstractDatastoreInitializer, and this
+ // build resolves that class from a released
grails-datamapping-core rather than from the
+ // source beside it -- so the call compiles here and goes missing
at run time.
+ neo4jDatastore(Neo4jDatastore, configuration,
ref("neo4jConnectionSourceFactory"), ref('grailsDatastoreEventPublisher'),
collectMappedClasses(DATASTORE_TYPE))
Review Comment:
`1ece28fbbb`. `getBeanDefinitions` calls
`refuseWhereTheConfigurationWouldBeWrittenOut()`, which throws
`IllegalStateException` when `AheadOfTimeProcessing.generatingCode`, naming
Neo4j as not yet AOT-processable.
Neither Neo4j change is compiled by anything: `grails-data-neo4j` is not in
the root `settings.gradle`, is not an `includeBuild`, is referenced by no
workflow, and its own build fails on
`org.apache.grails:grails-gradle-plugins:6.1.2`, which does not exist. It pins
`grailsVersion=6.0.0`, `groovyVersion=3.0.25`, `springBootVersion=2.7.18`,
`springVersion=5.3.31`, `spockVersion=2.1-groovy-3.0`,
`servletApiVersion=4.0.1`; 7 source files import `javax.*`, none import
`jakarta.*`. So `processAot` is not currently reachable for Neo4j at all.
##########
grails-data-neo4j/grails-plugin/src/main/groovy/grails/neo4j/bootstrap/Neo4jDataStoreSpringInitializer.groovy:
##########
@@ -75,17 +74,27 @@ class Neo4jDataStoreSpringInitializer extends
AbstractDatastoreInitializer {
callable.delegate = delegate
callable.call()
- ApplicationEventPublisher eventPublisher
- if (beanDefinitionRegistry instanceof
ConfigurableApplicationContext) {
- eventPublisher = new
ConfigurableApplicationContextEventPublisher((ConfigurableApplicationContext)
beanDefinitionRegistry)
- } else {
- eventPublisher = new DefaultApplicationEventPublisher()
- }
+ // Registered rather than built here, so what the datastore holds
is a reference the
+ // container can build. Holding the publisher itself puts a live
object in the
+ // definition, and generating code for a definition means writing
out what it holds --
+ // which a publisher bound to a running context is not.
+ //
+ // The choice is made here rather than through
AbstractDatastoreInitializer, because this
+ // build resolves that class from a released
grails-datamapping-core rather than from
+ // the source beside it, and a method added there is not one this
can call.
+ grailsDatastoreEventPublisher(beanDefinitionRegistry instanceof
ConfigurableApplicationContext
Review Comment:
`1ece28fbbb`. The condition now also tests
`resourcePatternResolver.resourceLoader instanceof
ConfigurableApplicationContext`, so it agrees with `findEventPublisherClass`.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]