Skip to content

Instantly share code, notes, and snippets.

@CaptainOfFlyingDutchman
Last active September 23, 2019 09:19
Show Gist options
  • Save CaptainOfFlyingDutchman/8b166b47514ca817d36e to your computer and use it in GitHub Desktop.
Save CaptainOfFlyingDutchman/8b166b47514ca817d36e to your computer and use it in GitHub Desktop.
Enable external configuration in Grails 3

External configuration in Grails 3


Working on Grails 3 I realized that I no longer can specify external configuration using the standard grails.config.locations property in Config.groovy file.

Reason is obvious! There is no Config.groovy now in Grails 3. Instead we now use application.yml to configure the properties. However, you can't specify the external configuration using this file too!

What the hack?

Now Grails 3 uses Spring's property source concept. To enable external config file to work we need to do something extra now.

Suppose I want to override some properties in application.yml file with my external configuration file.

E.g., from this:

 dataSource:
    username: sa
    password:
    driverClassName: "org.h2.Driver"

To this:

dataSource:
    username: root
    password: mysql
    driverClassName: "com.mysql.jdbc.Driver"

First I need to place this file in application's root. E.g., I've following structure of my Grails 3 application with external configuration file app-config.yml in place:

[human@machine tmp]$ tree -L 1 myapp
myapp
├── app-config.yml // <---- external configuration file!
├── build.gradle
├── gradle
├── gradle.properties
├── gradlew
├── gradlew.bat
├── grails-app
└── src
[human@machine tmp]$ 

Now, to enable reading this file just add following:

  • To your build.gradle file
bootRun {
    // local.config.location is just a random name. You can use yours.
    jvmArgs = ['-Dlocal.config.location=app-config.yml']
}
  • To your Application.groovy file
package com.mycompany

import grails.boot.GrailsApp
import grails.boot.config.GrailsAutoConfiguration
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean
import org.springframework.context.EnvironmentAware
import org.springframework.core.env.Environment
import org.springframework.core.env.PropertiesPropertySource
import org.springframework.core.io.FileSystemResource
import org.springframework.core.io.Resource

class Application extends GrailsAutoConfiguration implements EnvironmentAware {
    static void main(String[] args) {
        GrailsApp.run(Application, args)
    }

    @Override
    void setEnvironment(Environment environment) {
        String configPath = System.properties["local.config.location"]
        Resource resourceConfig = new FileSystemResource(configPath)
        YamlPropertiesFactoryBean ypfb = new YamlPropertiesFactoryBean()
        ypfb.setResources([resourceConfig] as Resource[])
        ypfb.afterPropertiesSet()
        Properties properties = ypfb.getObject()
        environment.propertySources.addFirst(new PropertiesPropertySource("local.config.location", properties))
    }
}

Notice that Application class implements EnvironmentAware Interface and overrides its setEnvironment(Environment environment):void method.

Now this is all what you need to re-enable external config file in Grails 3 application.

Code and guidance is taken from following links with little modification:

  1. http://grails.1312388.n4.nabble.com/Grails-3-External-config-td4658823.html
  2. https://groups.google.com/forum/#!topic/grails-dev-discuss/_5VtFz4SpDY

Cheers!!!

@gabehamilton
Copy link

Great gist, thanks. In my fork I added a check for whether the file exists in case the local config is optional.

@javagrails
Copy link

after [ grails prod war ] -> i didnt find any app-config.yml file in project root folder

@robertoschwald
Copy link

Isn't the spring-boot mechanism using SPRING_CONFIG_LOCATION env var sufficient?

@magnomp
Copy link

magnomp commented Dec 1, 2016

It's important to note that this mechanism and the default grails/springboot mechanism behave diferently when dealing with lists
While this:
`foo:
bar:
- a
- b

by default is loaded as foo.bar == ['a', 'b']`,
when loaded by this mechanism it's loaded as
foo.bar[1] == 'a'
foo.bar[2] == 'b'

It took me a lot of time to figure out this inconsistence

@magnomp
Copy link

magnomp commented Dec 1, 2016

Just found out that you can use YamlMapFactoryBean instead of YamlPropertiesFactoryBean to get the standard behaviour

@tyagiakhilesh
Copy link

tyagiakhilesh commented May 5, 2017

Better way can be using runtime.groovy file (to be placed alongside application.groovy and then I can do something below and it works alright.

dataSource {
    V8Compatible.patchV8Compatible()

    url = "jdbc:h2:mem:devDb" // DO NOT do 'def url = "...."', this shall be treated as a variable, not config option.
    driverClassName = "org.h2.Driver"
    username = "sa"
    password = ""
    pooled = true

    ConnectionService connectionService = new ConnectionService(false)
    RepositoryConnection.wireToService(connectionService)
    if (connectionService.init()) {
        Map connectionMap = connectionService.getConnectionMap()

        url = (String)connectionMap.get('url')
        driverClassName = (String)connectionMap.get('driverClassName')
        username = (String)connectionMap.get('username')
        password = (String)connectionMap.get('password')

        RDBMSType rdbmsType = connectionMap.get('rdbmsType');
        if (rdbmsType.getRDBMSName().contains("oracle")) {
            dialect = "org.hibernate.dialect.Oracle10gDialect"
        }
        OBTLog.debug('Connecting to url: ' + url)
        OBTLog.debug('  driverClassName: ' + driverClassName)
        OBTLog.debug('         username: ' + username)
    } else {
        // Use default h2 in mem database; don't override.
    }
}

println "DataSource being configured is $dataSource"

hibernate {
    cache {
        queries = false
        use_second_level_cache = true
        use_query_cache = false
        region {
            factory_class = org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory
        }
    }

    hbm2ddl {
        ConnectionService connectionService = new ConnectionService(false)
        RepositoryConnection.wireToService(connectionService)
        if (connectionService.init()) {
            auto = "update"
        } else {
            auto = "create"
        }
    }
}

@rstuven
Copy link

rstuven commented Aug 22, 2017

"This plugin will mimic the Grails 2 way of handling external configurations defined in grails.config.locations." http://plugins.grails.org/plugin/grails/external-config

@paulphilimone
Copy link

paulphilimone commented Dec 9, 2017

@javagrails as a point.

After "grails war" that "app-config.yml" file wont be inside the war or the deployed directory, and the configuration will not be loaded.

There is a workaround to that: on "src/main" directory create other dir named "resources" (src/main/resources) and put the configuration file (app-config.yml) there.

On Application.groovy class Load the file with this:

def url = getClass().classLoader.getResource("app-config.yml")
def confFile = new File(url.toURI())
Resource resourceConfig = new FileSystemResource(confFile)

Like that u will be able to load the external configuration file in production and development mode.

@bitsnaps
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment