Skip to content

Commit 3638832

Browse files
committed
feature: #14017 - add optional micronaut support via grails-micronaut
1 parent c5839b7 commit 3638832

File tree

32 files changed

+273
-150
lines changed

32 files changed

+273
-150
lines changed

gradle.properties

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,14 @@ org.gradle.daemon=true
5656
# this is a future TODO see groovydoc-tool-rewrite branch for experiementations with this
5757
org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Xmx5G
5858

59+
# micronaut libraries not in the bom due to the potential for spring mismatches
60+
micronautPlatformVersion=4.9.2
61+
# note: we do not import the micronaut bom in our tests to avoid spring version mismatches
62+
micronautHttpClientVersion=4.9.9
63+
micronautSerdeJacksonVersion=2.11.0
64+
5965
# libraries only specific to test apps, these should not be exposed
6066
jbossTransactionApiVersion=2.0.0.Final
61-
micronautVersion=4.6.5
62-
micronautSerdeJacksonVersion=2.11.0
6367
grailsSpringSecurityVersion=7.0.0-SNAPSHOT
6468

6569
# This prevents the Grails Gradle Plugin from unnecessarily excluding slf4j-simple in the generated POMs

gradle/publish-root-config.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ def publishedProjects = [
6565
'grails-interceptors',
6666
'grails-logging',
6767
'grails-mimetypes',
68+
'grails-micronaut',
6869
'grails-rest-transforms',
6970
'grails-scaffolding',
7071
'grails-services',

grails-core/src/main/groovy/grails/boot/GrailsApp.groovy

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,10 @@ class GrailsApp extends SpringApplication {
7272
* Create a new {@link GrailsApp} instance. The application context will load
7373
* beans from the specified sources (see {@link SpringApplication class-level}
7474
* documentation for details. The instance can be customized before calling
75-
* {@link #run(String...)}.
75+
* {@link #run(String ...)}.
7676
* @param sources the bean sources
77-
* @see #run(Object, String[])
78-
* @see #GrailsApp(org.springframework.core.io.ResourceLoader, Class<?>...)
77+
* @see #run(Class [ ], String [ ])
78+
* @see #GrailsApp(org.springframework.core.io.ResourceLoader, Class <?> ...)
7979
*/
8080
GrailsApp(Class<?>... sources) {
8181
super(sources)
@@ -85,11 +85,11 @@ class GrailsApp extends SpringApplication {
8585
* Create a new {@link GrailsApp} instance. The application context will load
8686
* beans from the specified sources (see {@link SpringApplication class-level}
8787
* documentation for details. The instance can be customized before calling
88-
* {@link #run(String...)}.
88+
* {@link #run(String ...)}.
8989
* @param resourceLoader the resource loader to use
9090
* @param sources the bean sources
91-
* @see #run(Object, String[])
92-
* @see #GrailsApp(org.springframework.core.io.ResourceLoader, Class<?>...)
91+
* @see #run(Class [ ], String [ ])
92+
* @see #GrailsApp(org.springframework.core.io.ResourceLoader, Class <?> ...)
9393
*/
9494
GrailsApp(ResourceLoader resourceLoader, Class<?>... sources) {
9595
super(resourceLoader, sources)
@@ -100,12 +100,12 @@ class GrailsApp extends SpringApplication {
100100
ConfigurableApplicationContext applicationContext = super.run(args)
101101
Environment environment = Environment.getCurrent()
102102

103-
log.info("Application starting in environment: {}", environment.getName())
104-
log.debug("Application directory discovered as: {}", IOUtils.findApplicationDirectory())
105-
log.debug("Current base directory is [{}]. Reloading base directory is [{}]", new File("."), BuildSettings.BASE_DIR)
103+
log.info('Application starting in environment: {}', environment.getName())
104+
log.debug('Application directory discovered as: {}', IOUtils.findApplicationDirectory())
105+
log.debug('Current base directory is [{}]. Reloading base directory is [{}]', new File('.'), BuildSettings.BASE_DIR)
106106

107107
if (environment.isReloadEnabled()) {
108-
log.debug("Reloading status: {}", environment.isReloadEnabled())
108+
log.debug('Reloading status: {}', environment.isReloadEnabled())
109109
enableDevelopmentModeWatch(environment, applicationContext)
110110
environment.isDevtoolsRestart()
111111
}
@@ -152,6 +152,7 @@ class GrailsApp extends SpringApplication {
152152
Queue<File> newFiles = new ConcurrentLinkedQueue<>()
153153

154154
directoryWatcher.addListener(new FileExtensionFileChangeListener(['groovy', 'java']) {
155+
155156
@Override
156157
void onChange(File file, List<String> extensions) {
157158
changedFiles << file.canonicalFile
@@ -162,7 +163,7 @@ class GrailsApp extends SpringApplication {
162163
changedFiles << file.canonicalFile
163164
// For some bizarre reason Windows fires onNew events even for files that have
164165
// just been modified and not created
165-
if (System.getProperty("os.name").toLowerCase().indexOf("windows") != -1) {
166+
if (System.getProperty('os.name').toLowerCase().indexOf('windows') != -1) {
166167
return
167168
}
168169
newFiles << file.canonicalFile
@@ -225,7 +226,6 @@ class GrailsApp extends SpringApplication {
225226
}
226227
}
227228

228-
229229
developmentModeActive = true
230230
Thread.start {
231231
CompilerConfiguration compilerConfig = new CompilerConfiguration()
@@ -302,12 +302,12 @@ class GrailsApp extends SpringApplication {
302302
}
303303
def baseFileLocation = appDir?.absolutePath ?: location
304304
compilerConfig.setTargetDirectory(new File(baseFileLocation, BuildSettings.BUILD_CLASSES_PATH))
305-
println "File $changedFile changed, recompiling..."
305+
printf('File %s changed, recompiling...%n', changedFile)
306306
if (changedFile.name.endsWith('.java')) {
307307
if (JavaCompiler.isAvailable()) {
308308
JavaCompiler.recompile(compilerConfig, changedFile)
309309
} else {
310-
log.error("Cannot recompile [$changedFile.name], the current JVM is not a JDK (recompilation will not work on a JRE missing the compiler APIs).")
310+
log.error('Cannot recompile [{}], the current JVM is not a JDK (recompilation will not work on a JRE missing the compiler APIs).', changedFile.name)
311311
}
312312
} else {
313313
compileGroovyFile(compilerConfig, changedFile)
@@ -335,6 +335,7 @@ class GrailsApp extends SpringApplication {
335335
protected static DirectoryWatcher.FileChangeListener createPluginManagerListener(ConfigurableApplicationContext applicationContext) {
336336
def pluginManager = applicationContext.getBean(GrailsPluginManager)
337337
return new DirectoryWatcher.FileChangeListener() {
338+
338339
@Override
339340
void onChange(File file) {
340341
if (!file.name.endsWith('.groovy') && !file.name.endsWith('.java')) {
@@ -352,9 +353,9 @@ class GrailsApp extends SpringApplication {
352353
}
353354

354355
protected void configureDirectoryWatcher(DirectoryWatcher directoryWatcher, String location) {
355-
directoryWatcher.addWatchDirectory(new File(location, "grails-app"), ['groovy', 'java'])
356-
directoryWatcher.addWatchDirectory(new File(location, "src/main/groovy"), ['groovy', 'java'])
357-
directoryWatcher.addWatchDirectory(new File(location, "src/main/java"), ['groovy', 'java'])
356+
directoryWatcher.addWatchDirectory(new File(location, 'grails-app'), ['groovy', 'java'])
357+
directoryWatcher.addWatchDirectory(new File(location, 'src/main/groovy'), ['groovy', 'java'])
358+
directoryWatcher.addWatchDirectory(new File(location, 'src/main/java'), ['groovy', 'java'])
358359
}
359360

360361
protected printRunStatus(ConfigurableApplicationContext applicationContext) {
@@ -363,11 +364,11 @@ class GrailsApp extends SpringApplication {
363364
String protocol = app.config.getProperty('server.ssl.key-store') ? 'https' : 'http'
364365
String contextPath = app.config.getProperty('server.servlet.context-path', '')
365366
String hostName = app.config.getProperty('server.address', 'localhost')
366-
int port
367+
int port = 0
367368
if (applicationContext instanceof WebServerApplicationContext) {
368369
port = applicationContext.webServer.port
369370
}
370-
println("Grails application running at ${protocol}://${hostName}:${port}${contextPath} in environment: ${Environment.current.name}")
371+
printf('Grails application running at %s://%s:%s%s in environment: %s%n', protocol, hostName, port, contextPath, Environment.current.name)
371372

372373
} catch (ignore) {
373374
}

grails-core/src/main/groovy/grails/boot/config/GrailsApplicationPostProcessor.groovy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import org.grails.config.PropertySourcesConfig
3939
import org.grails.core.exceptions.GrailsConfigurationException
4040
import org.grails.core.lifecycle.ShutdownOperations
4141
import org.grails.datastore.mapping.model.MappingContext
42+
import org.grails.plugins.core.CoreAutoConfiguration
4243
import org.grails.spring.DefaultRuntimeSpringConfiguration
4344
import org.grails.spring.RuntimeSpringConfigUtilities
4445
import org.springframework.beans.BeansException

grails-dependencies/starter-web/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def configurations = [
4747
':grails-gsp',
4848
':grails-i18n',
4949
':grails-interceptors',
50-
':grails-layout', // layout will be the default because it's fully functional while sitemesh3 still has issues
50+
System.getenv('SITEMESH3_TESTING_ENABLED') == 'true' ? ':grails-sitemesh3' : ':grails-layout',
5151
':grails-logging',
5252
':grails-rest-transforms',
5353
':grails-services',

grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/core/GrailsGradlePlugin.groovy

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension
2929
import org.apache.grails.gradle.common.PropertyFileUtils
3030
import org.apache.tools.ant.filters.EscapeUnicode
3131
import org.apache.tools.ant.filters.ReplaceTokens
32-
import org.gradle.api.Action
3332
import org.gradle.api.GradleException
3433
import org.gradle.api.NamedDomainObjectProvider
3534
import org.gradle.api.Plugin
@@ -72,10 +71,6 @@ import org.springframework.boot.gradle.tasks.bundling.BootArchive
7271
import org.springframework.boot.gradle.tasks.run.BootRun
7372

7473
import javax.inject.Inject
75-
import java.nio.charset.StandardCharsets
76-
import java.time.LocalDate
77-
import java.time.ZoneOffset
78-
import java.util.regex.Pattern
7974

8075
/**
8176
* The main Grails gradle plugin implementation
@@ -386,17 +381,21 @@ class GrailsGradlePlugin extends GroovyPlugin {
386381

387382
@CompileStatic
388383
protected void configureMicronaut(Project project) {
389-
final String micronautVersion = project.properties['micronautVersion']
390-
if (micronautVersion) {
391-
project.configurations.configureEach({ Configuration configuration ->
392-
configuration.resolutionStrategy.eachDependency({ DependencyResolveDetails details ->
393-
String dependencyName = details.requested.name
394-
String group = details.requested.group
395-
if (group == 'io.micronaut' && dependencyName.startsWith('micronaut')) {
396-
details.useVersion(micronautVersion)
397-
}
398-
} as Action<DependencyResolveDetails>)
399-
} as Action<Configuration>)
384+
project.afterEvaluate {
385+
boolean micronautEnabled = project.getConfigurations().getByName("implementation").getDependencies().findAll { Dependency dep -> dep.group == 'org.apache.grails' && dep.name == 'grails-micronaut' } as boolean
386+
if (!micronautEnabled) {
387+
return
388+
}
389+
project.logger.lifecycle('Micronaut Support Detected for {} - adding annotation processor dependencies for Micronaut', project.path)
390+
391+
final String micronautPlatformVersion = project.properties['micronautPlatformVersion']
392+
if (!micronautPlatformVersion) {
393+
throw new GradleException("`micronautPlatformVersion` property must be set to use the Grails Micronaut plugin.")
394+
}
395+
396+
project.getDependencies().add('annotationProcessor', project.dependencies.platform("io.micronaut.platform:micronaut-platform:$micronautPlatformVersion"))
397+
project.getDependencies().add('annotationProcessor', 'io.micronaut:micronaut-inject-java')
398+
project.getDependencies().add('annotationProcessor', 'jakarta.annotation:jakarta.annotation-api')
400399
}
401400
}
402401

grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/views/gsp/GroovyPageForkCompileTask.groovy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ abstract class GroovyPageForkCompileTask extends AbstractCompile {
133133
void execute(JavaExecSpec javaExecSpec) {
134134
javaExecSpec.mainClass.set(getCompilerName())
135135
javaExecSpec.setClasspath(getClasspath())
136+
//javaExecSpec.setJvmArgs(['-Xmx2g', '-Xdebug', '-Xnoagent','-Djava.compiler=NONE', '-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005'])
136137

137138
def jvmArgs = compileOptions.forkOptions.jvmArgs
138139
if (jvmArgs) {

grails-micronaut/build.gradle

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
plugins {
21+
id 'groovy'
22+
id 'java-library'
23+
id 'project-report'
24+
}
25+
26+
version = projectVersion
27+
group = 'org.apache.grails'
28+
29+
// force a local build version for debugging
30+
//project.configurations.configureEach({ Configuration configuration ->
31+
// configuration.resolutionStrategy.eachDependency({ DependencyResolveDetails details ->
32+
// String dependencyName = details.requested.name
33+
// String group = details.requested.group
34+
// if (group == 'io.micronaut' && dependencyName.startsWith('micronaut-core-processor')) {
35+
// logger.lifecycle("Forcing Micronaut Core Processor version to 4.9.99 for Grails Micronaut plugin")
36+
// details.useVersion('4.9.99')
37+
// }
38+
// } as Action<DependencyResolveDetails>)
39+
//} as Action<Configuration>)
40+
41+
dependencies {
42+
annotationProcessor platform(project(':grails-bom'))
43+
annotationProcessor platform("io.micronaut.platform:micronaut-platform:$micronautPlatformVersion")
44+
annotationProcessor 'io.micronaut:micronaut-inject-java'
45+
annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
46+
47+
compileOnlyApi platform("io.micronaut.platform:micronaut-platform:$micronautPlatformVersion")
48+
compileOnlyApi 'io.micronaut:micronaut-inject-groovy'
49+
compileOnlyApi 'io.micronaut:micronaut-inject-java'
50+
51+
api platform("io.micronaut.platform:micronaut-platform:$micronautPlatformVersion")
52+
api 'io.micronaut.spring:micronaut-spring-boot-starter'
53+
api "io.micronaut.spring:micronaut-spring-context", {
54+
// required to load the micronaut environment with plugin configuration
55+
exclude group: "org.slf4j", module: "slf4j-simple"
56+
}
57+
api "io.micronaut.cache:micronaut-cache-core", {
58+
exclude group:"org.slf4j", module: "slf4j-simple"
59+
}
60+
61+
implementation platform(project(':grails-bom'))
62+
compileOnly project(':grails-core')
63+
64+
// Logging
65+
compileOnly 'org.slf4j:slf4j-api'
66+
compileOnly 'org.slf4j:jcl-over-slf4j'
67+
68+
// Testing
69+
testImplementation platform(project(':grails-bom'))
70+
testImplementation 'org.slf4j:slf4j-simple'
71+
testImplementation('org.spockframework:spock-core') { transitive = false }
72+
73+
// Required by Spock's Mocking
74+
testRuntimeOnly 'net.bytebuddy:byte-buddy'
75+
testImplementation 'org.objenesis:objenesis'
76+
}
77+
78+
apply {
79+
// java-configuration must be applied first since tasks are now lazy registered
80+
from rootProject.layout.projectDirectory.file('gradle/java-config.gradle')
81+
from rootProject.layout.projectDirectory.file('gradle/docs-config.gradle')
82+
from rootProject.layout.projectDirectory.file('gradle/publish-config.gradle')
83+
from rootProject.layout.projectDirectory.file('gradle/test-config.gradle')
84+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.grails.micronaut
21+
22+
import grails.plugins.GrailsPlugin
23+
import grails.plugins.GrailsPluginManager
24+
import grails.plugins.Plugin
25+
import groovy.util.logging.Slf4j
26+
import io.micronaut.context.ConfigurableApplicationContext
27+
import io.micronaut.context.env.AbstractPropertySourceLoader
28+
import io.micronaut.context.env.Environment
29+
import io.micronaut.context.env.PropertySource
30+
31+
@Slf4j
32+
class GrailsMicronautGrailsPlugin extends Plugin {
33+
34+
def grailsVersion = '7.0.0-SNAPSHOT > *'
35+
def title = 'Grails Micronaut Plugin'
36+
37+
@Override
38+
void doWithApplicationContext() {
39+
String[] beanNames = applicationContext.getBeanNamesForType(GrailsPluginManager)
40+
GrailsPluginManager pluginManagerFromContext = beanNames.length ?
41+
applicationContext.getBean(GrailsPluginManager) :
42+
null
43+
44+
if (!pluginManagerFromContext && !pluginManager) {
45+
// No plugin managers to search for plugin configurations
46+
return
47+
}
48+
49+
if (!applicationContext.containsBean('micronautApplicationContext')) {
50+
throw new IllegalStateException("A Micronaut Application Context should exist prior to the loading of the Grails Micronaut plugin.")
51+
}
52+
53+
ConfigurableApplicationContext micronautContext = applicationContext.getBean('micronautApplicationContext', ConfigurableApplicationContext)
54+
Environment micronautEnv = micronautContext.getEnvironment()
55+
56+
log.debug("Loading configurations from the plugins to the parent Micronaut context")
57+
final GrailsPlugin[] plugins = pluginManager.allPlugins
58+
final GrailsPlugin[] pluginsFromContext = pluginManagerFromContext ? pluginManagerFromContext.allPlugins : new GrailsPlugin[]{}
59+
Integer priority = AbstractPropertySourceLoader.DEFAULT_POSITION
60+
[plugins, pluginsFromContext].each { GrailsPlugin[] pluginsToProcess ->
61+
Arrays.stream(pluginsToProcess)
62+
.filter({ GrailsPlugin plugin -> plugin.propertySource != null })
63+
.forEach({ GrailsPlugin plugin ->
64+
if (log.isDebugEnabled()) {
65+
log.debug("Loading configurations from {} plugin to the parent Micronaut context", plugin.name)
66+
}
67+
// If invoking the source as `.source`, the NavigableMapPropertySource will return null, while invoking the getter, it will return the correct value
68+
micronautEnv.addPropertySource(PropertySource.of("grails.plugins.$plugin.name", (Map) plugin.propertySource.getSource(), --priority))
69+
})
70+
}
71+
micronautEnv.refresh()
72+
// applicationContext.setParent(parentApplicationContext)
73+
}
74+
}

grails-spring/src/main/groovy/org/grails/spring/GrailsApplicationContext.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,13 @@ public GrailsApplicationContext() throws org.springframework.beans.BeansExceptio
7171

7272
@Override
7373
public boolean containsBeanDefinition(String beanName) {
74-
return super.containsBeanDefinition(beanName);
74+
if(super.containsBeanDefinition(beanName)) {
75+
return true;
76+
} else if (getParent() != null && "grailsApplication".equals(beanName)) {
77+
return getParent().containsBeanDefinition(beanName);
78+
} else {
79+
return false;
80+
}
7581
}
7682

7783
public MetaClass getMetaClass() {

0 commit comments

Comments
 (0)