grails.boot.GrailsApp.groovy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of grails-core Show documentation
Show all versions of grails-core Show documentation
Grails Web Application Framework
package grails.boot
import grails.compiler.ast.ClassInjector
import grails.core.GrailsApplication
import grails.io.IOUtils
import grails.plugins.GrailsPlugin
import grails.plugins.GrailsPluginManager
import grails.util.BuildSettings
import grails.util.Environment
import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import io.micronaut.context.ApplicationContext
import io.micronaut.context.ApplicationContextBuilder
import io.micronaut.context.ApplicationContextConfiguration
import io.micronaut.context.env.AbstractPropertySourceLoader
import io.micronaut.context.env.PropertySource
import io.micronaut.core.util.StringUtils
import io.micronaut.spring.context.env.MicronautEnvironment
import io.micronaut.spring.context.factory.MicronautBeanFactoryConfiguration
import org.codehaus.groovy.control.CompilationFailedException
import org.codehaus.groovy.control.CompilationUnit
import org.codehaus.groovy.control.CompilerConfiguration
import org.grails.boot.internal.JavaCompiler
import org.grails.compiler.injection.AbstractGrailsArtefactTransformer
import org.grails.compiler.injection.GrailsAwareInjectionOperation
import org.grails.core.util.BeanCreationProfilingPostProcessor
import org.grails.io.watch.DirectoryWatcher
import org.grails.io.watch.FileExtensionFileChangeListener
import org.grails.plugins.BinaryGrailsPlugin
import org.grails.plugins.support.WatchPattern
import org.springframework.boot.Banner
import org.springframework.boot.ResourceBanner
import org.springframework.boot.SpringApplication
import org.springframework.boot.context.event.ApplicationPreparedEvent
import org.springframework.boot.web.context.WebServerApplicationContext
import org.springframework.context.ApplicationListener
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.context.event.ContextStoppedEvent
import org.springframework.core.convert.ConversionService
import org.springframework.core.env.ConfigurableEnvironment
import org.springframework.core.env.PropertyResolver
import org.springframework.core.io.ClassPathResource
import org.springframework.core.io.ResourceLoader
import java.util.concurrent.ConcurrentLinkedQueue
/**
* Extends the {@link SpringApplication} with reloading behavior and other Grails features
*
*
* @author Graeme Rocher
* @since 3.0
*/
@CompileStatic
@Slf4j
class GrailsApp extends SpringApplication {
private static final String GRAILS_BANNER = 'grails-banner.txt'
private static final String SPRING_PROFILES = 'spring.profiles.active'
private static boolean developmentModeActive = false
private static DirectoryWatcher directoryWatcher
boolean enableBeanCreationProfiler = false
ConfigurableEnvironment configuredEnvironment
/**
* Create a new {@link GrailsApp} instance. The application context will load
* beans from the specified sources (see {@link SpringApplication class-level}
* documentation for details. The instance can be customized before calling
* {@link #run(String...)}.
* @param sources the bean sources
* @see #run(Object, String[])
* @see #GrailsApp(org.springframework.core.io.ResourceLoader, Class>...)
*/
GrailsApp(Class>... sources) {
super(sources)
bannerMode = Banner.Mode.OFF
}
/**
* Create a new {@link GrailsApp} instance. The application context will load
* beans from the specified sources (see {@link SpringApplication class-level}
* documentation for details. The instance can be customized before calling
* {@link #run(String...)}.
* @param resourceLoader the resource loader to use
* @param sources the bean sources
* @see #run(Object, String[])
* @see #GrailsApp(org.springframework.core.io.ResourceLoader, Class>...)
*/
GrailsApp(ResourceLoader resourceLoader, Class>... sources) {
super(resourceLoader, sources)
bannerMode = Banner.Mode.OFF
}
@Override
ConfigurableApplicationContext run(String... args) {
ConfigurableApplicationContext applicationContext = super.run(args)
Environment environment = Environment.getCurrent()
log.info("Application starting in environment: {}", environment.getName())
log.debug("Application directory discovered as: {}", IOUtils.findApplicationDirectory())
log.debug("Current base directory is [{}]. Reloading base directory is [{}]", new File("."), BuildSettings.BASE_DIR)
loadPluginConfigurationsToMicronautContext(applicationContext)
if(environment.isReloadEnabled()) {
log.debug("Reloading status: {}", environment.isReloadEnabled())
enableDevelopmentModeWatch(environment, applicationContext)
environment.isDevtoolsRestart()
}
printRunStatus(applicationContext)
return applicationContext
}
@SuppressWarnings("GrMethodMayBeStatic")
private void loadPluginConfigurationsToMicronautContext(ConfigurableApplicationContext applicationContext) {
GrailsPluginManager pluginManager = applicationContext.getBean(GrailsPluginManager)
ConfigurableApplicationContext parentApplicationContext = (ConfigurableApplicationContext) applicationContext.parent
ConfigurableEnvironment parentContextEnv = parentApplicationContext.getEnvironment()
if (parentContextEnv instanceof MicronautEnvironment) {
if (log.isDebugEnabled()) {
log.debug("Loading configurations from the plugins to the parent Micronaut context")
}
final io.micronaut.context.env.Environment micronautEnv = ((io.micronaut.context.env.Environment) parentContextEnv.getEnvironment())
final GrailsPlugin[] plugins = pluginManager.allPlugins
Integer priority = AbstractPropertySourceLoader.DEFAULT_POSITION
Arrays.stream(plugins)
.filter({ GrailsPlugin plugin -> plugin.propertySource != null })
.forEach({ GrailsPlugin plugin ->
if (log.isDebugEnabled()) {
log.debug("Loading configurations from {} plugin to the parent Micronaut context", plugin.name)
}
micronautEnv.addPropertySource(PropertySource.of("grails.plugins.$plugin.name", (Map) plugin.propertySource.source, --priority))
})
micronautEnv.refresh()
applicationContext.setParent(parentApplicationContext)
}
}
@Override
protected ConfigurableApplicationContext createApplicationContext() {
setAllowBeanDefinitionOverriding(true)
setAllowCircularReferences(true)
ConfigurableApplicationContext applicationContext = super.createApplicationContext()
def now = System.currentTimeMillis()
ClassLoader applicationClassLoader = this.classLoader
ApplicationContextConfiguration micronautConfiguration = new ApplicationContextConfiguration() {
@Override
List getEnvironments() {
if (configuredEnvironment != null) {
return configuredEnvironment.getActiveProfiles().toList()
} else {
return Collections.emptyList()
}
}
@Override
Optional getDeduceEnvironments() {
return Optional.of(false)
}
@Override
ClassLoader getClassLoader() {
return applicationClassLoader
}
}
List beanExcludes = []
beanExcludes.add(ConversionService.class)
beanExcludes.add(org.springframework.core.env.Environment.class)
beanExcludes.add(PropertyResolver.class)
beanExcludes.add(ConfigurableEnvironment.class)
def objectMapper = io.micronaut.core.reflect.ClassUtils.forName("com.fasterxml.jackson.databind.ObjectMapper", classLoader).orElse(null)
if (objectMapper != null) {
beanExcludes.add(objectMapper)
}
def micronautContext = new io.micronaut.context.DefaultApplicationContext(micronautConfiguration);
micronautContext
.environment
.addPropertySource("grails-config", [(MicronautBeanFactoryConfiguration.PREFIX + ".bean-excludes"): (Object)beanExcludes])
micronautContext.start()
ConfigurableApplicationContext parentContext = micronautContext.getBean(ConfigurableApplicationContext)
applicationContext.setParent(
parentContext
)
applicationContext.addApplicationListener(new MicronautShutdownListener(micronautContext))
log.info("Started Micronaut Parent Application Context in ${System.currentTimeMillis()-now}ms")
if(enableBeanCreationProfiler) {
def processor = new BeanCreationProfilingPostProcessor()
applicationContext.getBeanFactory().addBeanPostProcessor(processor)
applicationContext.addApplicationListener(processor)
}
return applicationContext
}
protected ApplicationContextBuilder newMicronautContextBuilder() {
return ApplicationContext.builder()
}
@Override
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
configurePropertySources(environment, args)
String[] springProfile = environment.getProperty(SPRING_PROFILES, String[])
if (springProfile) {
environment.setActiveProfiles(springProfile)
}
def env = Environment.current
environment.addActiveProfile(env.name)
configuredEnvironment = environment
}
@CompileDynamic // TODO: Report Groovy VerifierError
protected void enableDevelopmentModeWatch(Environment environment, ConfigurableApplicationContext applicationContext, String... args) {
def location = environment.getReloadLocation()
if(location) {
directoryWatcher = new DirectoryWatcher()
Queue changedFiles = new ConcurrentLinkedQueue<>()
Queue newFiles = new ConcurrentLinkedQueue<>()
directoryWatcher.addListener(new FileExtensionFileChangeListener(['groovy', 'java']) {
@Override
void onChange(File file, List extensions) {
changedFiles << file.canonicalFile
}
@Override
void onNew(File file, List extensions) {
changedFiles << file.canonicalFile
// For some bizarro reason Windows fires onNew events even for files that have
// just been modified and not created
if(System.getProperty("os.name").toLowerCase().indexOf("windows") != -1) {
return
}
newFiles << file.canonicalFile
}
})
def pluginManager = applicationContext.getBean(GrailsPluginManager)
def pluginManagerListener = createPluginManagerListener(applicationContext)
directoryWatcher.addListener(pluginManagerListener)
File baseDir = new File(location).canonicalFile
String baseDirPath = baseDir.canonicalPath
List watchBaseDirectories = [baseDir]
for(GrailsPlugin plugin in pluginManager.allPlugins) {
if(plugin instanceof BinaryGrailsPlugin) {
BinaryGrailsPlugin binaryGrailsPlugin = (BinaryGrailsPlugin)plugin
def pluginDirectory = binaryGrailsPlugin.projectDirectory
if(pluginDirectory != null) {
watchBaseDirectories << pluginDirectory
}
}
}
for(dir in watchBaseDirectories) {
configureDirectoryWatcher(directoryWatcher, dir.absolutePath)
}
for(GrailsPlugin plugin in pluginManager.allPlugins) {
def watchedResourcePatterns = plugin.getWatchedResourcePatterns()
if(watchedResourcePatterns != null) {
for(WatchPattern wp in new ArrayList(watchedResourcePatterns)) {
boolean first = true
for(watchBase in watchBaseDirectories) {
if(!first) {
if(wp.file != null) {
String relativePath = wp.file.canonicalPath - baseDirPath
File watchFile = new File(watchBase, relativePath)
// the base project will already been in the list of watch patterns, but we add any subprojects here
plugin.watchedResourcePatterns.add(new WatchPattern(file: watchFile, extension: wp.extension))
}
else if(wp.directory != null) {
String relativePath = wp.directory.canonicalPath - baseDirPath
File watchDir = new File(watchBase, relativePath)
// the base project will already been in the list of watch patterns, but we add any subprojects here
plugin.watchedResourcePatterns.add(new WatchPattern(directory: watchDir, extension: wp.extension))
}
}
first = false
if(wp.file) {
String relativePath = wp.file.canonicalPath - baseDirPath
def resolvedPath = new File(watchBase, relativePath)
directoryWatcher.addWatchFile(resolvedPath)
}
else if(wp.directory && wp.extension) {
String relativePath = wp.directory.canonicalPath - baseDirPath
def resolvedPath = new File(watchBase, relativePath)
directoryWatcher.addWatchDirectory(resolvedPath, wp.extension)
}
}
}
}
}
developmentModeActive = true
Thread.start {
CompilerConfiguration compilerConfig = new CompilerConfiguration()
compilerConfig.setTargetDirectory(new File(location, BuildSettings.BUILD_CLASSES_PATH))
while(isDevelopmentModeActive()) {
// Workaround for some IDE / OS combos - 2 events (new + update) for the same file
def uniqueChangedFiles = changedFiles as Set
def i = uniqueChangedFiles.size()
try {
if(i > 1) {
changedFiles.clear()
for(f in uniqueChangedFiles) {
recompile(f, compilerConfig, location)
if(newFiles.contains(f)) {
newFiles.remove(f)
}
pluginManager.informOfFileChange(f)
sleep 1000
}
}
else if(i == 1) {
changedFiles.clear()
def changedFile = uniqueChangedFiles[0]
changedFile = changedFile.canonicalFile
// Groovy files within the 'conf' directory are not compiled
String confPath = "${File.separator}grails-app${File.separator}conf${File.separator}"
if(changedFile.path.contains(confPath)) {
pluginManager.informOfFileChange(changedFile)
}
else {
recompile(changedFile, compilerConfig, location)
if(newFiles.contains(changedFile)) {
newFiles.remove(changedFile)
}
pluginManager.informOfFileChange(changedFile)
}
}
newFiles.clear()
} catch (CompilationFailedException cfe) {
log.error("Compilation Error: $cfe.message", cfe)
}
sleep 1000
}
}
directoryWatcher.start()
}
}
static boolean isDevelopmentModeActive() {
return developmentModeActive
}
static void setDevelopmentModeActive(boolean active) {
developmentModeActive = active
if(directoryWatcher != null) {
directoryWatcher.active = active
}
}
protected void recompile(File changedFile, CompilerConfiguration compilerConfig, String location) {
File appDir = null
def changedPath = changedFile.path
String grailsAppDir = "${File.separator}grails-app"
String sourceMainGroovy = "${File.separator}src${File.separator}main${File.separator}groovy"
if (changedPath.contains(grailsAppDir)) {
appDir = new File(changedPath.substring(0, changedPath.indexOf(grailsAppDir)))
}
else if(changedPath.contains(sourceMainGroovy)) {
appDir = new File(changedPath.substring(0, changedPath.indexOf(sourceMainGroovy)))
}
def baseFileLocation = appDir?.absolutePath ?: location
compilerConfig.setTargetDirectory(new File(baseFileLocation, BuildSettings.BUILD_CLASSES_PATH))
println "File $changedFile changed, recompiling..."
if(changedFile.name.endsWith('.java')) {
if(JavaCompiler.isAvailable()) {
JavaCompiler.recompile(compilerConfig, changedFile)
}
else {
log.error("Cannot recompile [$changedFile.name], the current JVM is not a JDK (recompilation will not work on a JRE missing the compiler APIs).")
}
}
else {
compileGroovyFile(compilerConfig, changedFile)
}
}
protected void compileGroovyFile(CompilerConfiguration compilerConfig, File changedFile) {
ClassInjector[] classInjectors = GrailsAwareInjectionOperation.getClassInjectors()
for (ClassInjector classInjector in classInjectors) {
if (classInjector instanceof AbstractGrailsArtefactTransformer) {
((AbstractGrailsArtefactTransformer) classInjector).clearCachedState()
}
}
// only one change, just to a simple recompile and propagate the change
def unit = new CompilationUnit(compilerConfig)
unit.addSource(changedFile)
unit.compile()
}
/**
* Creates and returns a file change listener for notifying the plugin manager of changes.
* @param applicationContext - The running {@link org.springframework.context.ApplicationContext}
* @return {@link DirectoryWatcher.FileChangeListener}
*/
protected static DirectoryWatcher.FileChangeListener createPluginManagerListener(ConfigurableApplicationContext applicationContext) {
def pluginManager = applicationContext.getBean(GrailsPluginManager)
return new DirectoryWatcher.FileChangeListener() {
@Override
void onChange(File file) {
if(!file.name.endsWith('.groovy') && !file.name.endsWith('.java')) {
pluginManager.informOfFileChange(file)
}
}
@Override
void onNew(File file) {
if(!file.name.endsWith('.groovy') && !file.name.endsWith('.java')) {
pluginManager.informOfFileChange(file)
}
}
}
}
protected void configureDirectoryWatcher(DirectoryWatcher directoryWatcher, String location) {
directoryWatcher.addWatchDirectory(new File(location, "grails-app"), ['groovy', 'java'])
directoryWatcher.addWatchDirectory(new File(location, "src/main/groovy"), ['groovy', 'java'])
directoryWatcher.addWatchDirectory(new File(location, "src/main/java"), ['groovy', 'java'])
}
protected printRunStatus(ConfigurableApplicationContext applicationContext) {
try {
GrailsApplication app = applicationContext.getBean(GrailsApplication)
String protocol = app.config.getProperty('server.ssl.key-store') ? 'https' : 'http'
applicationContext.publishEvent(
new ApplicationPreparedEvent(
this,
StringUtils.EMPTY_STRING_ARRAY, (ConfigurableApplicationContext)applicationContext.getParent())
)
String contextPath = app.config.getProperty('server.servlet.context-path', '')
String hostName = app.config.getProperty('server.address', 'localhost')
int port
if (applicationContext instanceof WebServerApplicationContext) {
port = applicationContext.webServer.port
}
println("Grails application running at ${protocol}://${hostName}:${port}${contextPath} in environment: ${Environment.current.name}")
} catch (e) {
// ignore
}
}
/**
* Static helper that can be used to run a {@link GrailsApp} from the
* specified source using default settings.
* @param source the source to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link org.springframework.context.ApplicationContext}
*/
static ConfigurableApplicationContext run(Class> source, String... args) {
return run([ source ] as Class[], args)
}
/**
* Static helper that can be used to run a {@link GrailsApp} from the
* specified sources using default settings and user supplied arguments.
* @param sources the sources to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link org.springframework.context.ApplicationContext}
*/
static ConfigurableApplicationContext run(Class>[] sources, String[] args) {
GrailsApp grailsApp = new GrailsApp(sources)
grailsApp.banner = new ResourceBanner(new ClassPathResource(GRAILS_BANNER))
return grailsApp.run(args)
}
@CompileStatic
static class MicronautShutdownListener implements ApplicationListener {
final ApplicationContext micronautApplicationContext
MicronautShutdownListener(ApplicationContext micronautApplicationContext) {
this.micronautApplicationContext = micronautApplicationContext
}
@Override
void onApplicationEvent(ContextStoppedEvent event) {
this.micronautApplicationContext.stop()
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy