How can I use a properties file in the "expand" portion of the Gradle Copy task? - gradle

Lets say I have properties file config.properties which has
prop1=abc
prop2=xyz
and a template-config.xml that looks something like
<bean id="id1" >
<property name="prop1" value="${prop1}" />
<property name="prop2" value="${prop2}" />
</bean>
I have 2 Questions:
Is there a way I can use the property file in the expand() portion of the gradle copy task to inject the properties into the config from gradle.build.kts?
Is there a way I can use expand to fill in only one of the properties without throwing an error?
So far I have
tasks.register<Copy>("create-config-from-template") {
from("$buildDir/resources/main/template-config.xml")
into("$buildDir/dist")
expand(Pair("prop1", "abc"))
}
However, this throws an error
Missing property (prop2) for Groovy template expansion. Defined keys [prop1].
I know that I can also specify the value for prop2 inside "expand()", but for my purposes it would help if I could only inject some of the properties and not others. Is there a simple way to tell gradle not to worry about the other "${}" properties in the file?
If not, is there a way I can use the actual property file as the set of properties to expand? I can't seem to find the Kotlin DSL syntax for this anywhere.
Thank you very much in advance.

For 1, you can use configure the existing processResources task to apply additional configuration to a specific file. In this case template-config.xml, for example:
tasks.processResources {
filesMatching("**/template-config.xml") {
val examplePropertiesTextResource = resources.text.fromFile(layout.projectDirectory.file("example.properties"))
val exampleProperties = Properties().apply {
FileInputStream(examplePropertiesTextResource.asFile()).use { load(it) }
}
val examplePropertiesMap = mutableMapOf<String, Any>().apply {
exampleProperties.forEach { k, v -> put(k.toString(), v) }
}
expand(examplePropertiesMap)
}
}
filesMatching will match on the file you want. Then the proceeding lines load the properties file, in this case a file name example.properties in the project's root directory. It is then transformed into a Map<String, Any> and passed to the expand method.
However, for 2, with the above approach it will fail if any properties are missing. This is because the underlying expansion engine (SimpleTemplateEngine) does not allow such configuration
As an alternative, you can use ExpandTokens from Apache ANT to achieve the replacement when some properties are missing:
tasks.processResources {
filesMatching("**/template-config.xml") {
val examplePropertiesTextResource = resources.text.fromFile(layout.projectDirectory.file("example.properties"))
val exampleProperties = Properties().apply {
FileInputStream(examplePropertiesTextResource.asFile()).use { load(it) }
}
val examplePropertiesMap = mapOf<String, Any>("tokens" to exampleProperties.toMap())
println(examplePropertiesMap)
filter(examplePropertiesMap, ReplaceTokens::class.java)
}
}
Note that for ReplaceTokens, you must change the placeholders from ${example} to #example#:
<bean id="id1" >
<property name="prop1" value="#prop1#" />
<property name="prop2" value="#prop2#" />
</bean>
This is because Gradle only accepts a Class and ReplaceTokens is final, so you can't extend the class to use ${} as the placeholders.

Related

Specifying rootPath for Liquibase via jOOQ generation

I'm trying to utilize jOOQ's ability to generate from Liquibase files. My file structure is as follows:
C
- dev
-- testproject
--- src/main/resources
---- db
----- changelog.xml
In order to reference this file from the jOOQ configuration, I have the following in my build.gradle.kts:
jooq {
configurations {
create("main") {
jooqConfiguration.apply {
generator.apply {
database.apply {
name = "org.jooq.meta.extensions.liquibase.LiquibaseDatabase"
properties.add(Property().apply {
key = "rootPath"
value = "C:/dev/testproject/src/main/resources/db/"
})
properties.add(Property().apply {
key = "scripts"
value = "changelog.xml"
})
}
}
}
}
}
}
I'm using plugin version 7.1.1 and have the following dependencies:
dependencies {
implementation("org.liquibase:liquibase-core:4.8.0") // I tried removing this, no change
jooqGenerator("org.postgresql:postgresql:42.3.2")
jooqGenerator("org.jooq:jooq-meta-extensions-liquibase:3.17.2")
jooqGenerator(files("src/main/resources")) // I don't think this is necessary
}
When I try to run jooqGenerate, the error I get is:
Caused by: liquibase.exception.ChangeLogParseException: The file changelog.xml was not found in
Specifying files by absolute path was removed in Liquibase 4.0. Please use a relative path or add '/' to the classpath parameter.
at liquibase.parser.core.xml.XMLChangeLogSAXParser.parseToNode(XMLChangeLogSAXParser.java:82)
at liquibase.parser.core.xml.AbstractChangeLogParser.parse(AbstractChangeLogParser.java:15)
at liquibase.Liquibase.getDatabaseChangeLog(Liquibase.java:369)
Notice how it doesn't say which directories it looked in. As far as I can tell, the resource accessor is not receiving the rootPath from the configuration. The relevant output from Liquibase is here. Again, it should say it looked in the rootPath, but it doesn't print anything else, so there must be no directories searched.
Not sure if this is helpful, but the jOOQ configuration file in build/tmp/generateJooq definitely has the rootPath:
<property>
<key>rootPath</key>
<value>C:/dev/testproject/src/main/resources/db/</value>
</property>
I'm not sure where I'm going wrong. I've also tried the following values of scripts without setting rootPath and seen the same behavior:
C:/dev/testproject/src/main/resources/db/changelog.xml
src/main/resources/db/changelog.xml
/src/main/resources/db/changelog.xml
classpath:src/main/resources/db/changelog.xml
classpath:/src/main/resources/db/changelog.xml
This was causing the problem (or rather, the confusion):
jooqGenerator(files("src/main/resources"))
Apparently, this sets the classpath of the jooqGenerator task to be src/main/resources! So, knowing that, I fixed my configuration to look like this:
database.apply {
name = "org.jooq.meta.extensions.liquibase.LiquibaseDatabase"
properties.add(Property().apply {
key = "scripts"
value = "classpath:db/changelog.xml"
})
}
Everything is working nicely now.

In gradle kotlin dsl, how to call a dynamic test extension?

I'm trying to add a new configuration option when using the gradle ScalaTest plugin:
https://github.com/maiflai/gradle-scalatest
In its source code, the config was injected into the Test class as a dynamic extension:
static void configure(Test test) {
...
Map<String, ?> config = [:]
test.extensions.add(ScalaTestAction.CONFIG, config)
test.extensions.add("config", { String name, value -> config.put(name, value) })
test.extensions.add("configMap", { Map<String, ?> c -> config.putAll(c) })
...
}
If using groovy as the dsl, calling this property is easy:
test {
configMap([
'db.name': 'testdb'
'server': '192.168.1.188'
])
}
unfortunately the kotlin dsl can't use this method due to static typing, when being invoked as a test plugin, it is clearly visible within the test scope, e.g. when using extensions.getByName:
tasks {
test {
val map = extensions.getByName("configMap")
println(map)
}
}
It yields the following output:
...
> Configure project :
com.github.maiflai.ScalaTestPlugin$_configure_closure6#45c21cac
But there is no way to retrieve or assert its type in compile time, and it ends up being useless (unless reflection is used, which is against the design philosophy of kotlin dsl). Is there a easy way for kotlin dsl to achieve the same?
I saw in the Scala test gradle plugin that the dynamic extension is defined like this:
test.extensions.add("configMap", { Map<String, ?> c -> config.putAll(c) })
The com.github.maiflai.ScalaTestPlugin$_configure_closure6#45c21cac you saw should be a closure of type (Map<String, Any>) -> Unit, which means you can do that. We'll have to change the map values so let's assume that it's also mutable.
extensions.getByName("configMap").closureOf<MutableMap<String, Any?>> {
this["db.name"] = "testdb"
this["server"] = "192.168.1.188"
}
This builds fine but I don't have Scala installed and never used Scala test. I have no idea if it actually works, so please tell me.

ConfigurationProperties loading list from YML

I'm trying to load Configuration from YML. I can load value and I can also load list if these are comma seperated values. But i can't load a typical YML List.
Configuration Class
#Component
#PropertySource("classpath:routing.yml")
#ConfigurationProperties
class RoutingProperties(){
var angular = listOf("nothing")
var value: String = ""
}
Working routing.yml
angular: /init, /home
value: Hello World
Not Working routing.yml
angular:
- init
- home
value: Hello World
Why can't i load the second version of yml / do I have a syntaxt error?
ENV: Kotlin, Spring 2.0.0.M3
As #flyx say, #PropetySource not worked with yaml files. But in spring you may override almost everything :)
PropertySource has additional parameter: factory. It's possible to create your own PropertySourceFactory base on DefaultPropertySourceFactory
open class YamlPropertyLoaderFactory : DefaultPropertySourceFactory() {
override fun createPropertySource(name: String?, resource: EncodedResource?): org.springframework.core.env.PropertySource<*> {
if (resource == null)
return super.createPropertySource(name, resource)
return YamlPropertySourceLoader().load(resource.resource.filename, resource.resource, null)
}
}
And when use this factory in propertysource annotation:
#PropertySource("classpath:/routing.yml", factory = YamlPropertyLoaderFactory::class)
Last that you need is to initialized variable angular with mutableList
Full code sample:
#Component
#PropertySource("classpath:/routing.yml", factory = YamlPropertyLoaderFactory::class)
#ConfigurationProperties
open class RoutingProperties {
var angular = mutableListOf("nothing")
var value: String = ""
override fun toString(): String {
return "RoutingProperties(angular=$angular, value='$value')"
}
}
open class YamlPropertyLoaderFactory : DefaultPropertySourceFactory() {
override fun createPropertySource(name: String?, resource: EncodedResource?): org.springframework.core.env.PropertySource<*> {
if (resource == null)
return super.createPropertySource(name, resource)
return YamlPropertySourceLoader().load(resource.resource.filename, resource.resource, null)
}
}
#SpringBootApplication
#EnableAutoConfiguration(exclude = arrayOf(DataSourceAutoConfiguration::class))
open class Application {
companion object {
#JvmStatic
fun main(args: Array<String>) {
val context = SpringApplication.run(Application::class.java, *args)
val bean = context.getBean(RoutingProperties::class.java)
println(bean)
}
}
}
Kinda old post, i know. But i am at the very same topic right now.
As of now, it seems that PropertySource does indeed work with yaml Files. Given the restriction that it only allows for primitive types (it seems) and it cant handle nested elements. I'm probably gonna dig a bit deeper and update my answer accordingly, but as of now, the accepted answer seems like a functioning workaround.
Well, according to the docs, your YAML file will be rewritten into a property file. The first YAML file becomes:
angular=/init, /home
value=Hello World
While the second one becomes:
angular[0]=init
angular[1]=home
value=Hello World
These are obviously two very different things and therefore behave differently.
Moreover, later in the docs, it is stated that YAML does not even work with #PropertySource:
24.6.4 YAML shortcomings
YAML files can’t be loaded via the #PropertySource annotation. So in the case that you need to load values that way, you need to use a properties file.
That makes me kind of wonder why the first case works for you at all.
The docs say this about the generated …[index] properties:
To bind to properties like that using the Spring DataBinder utilities (which is what #ConfigurationProperties does) you need to have a property in the target bean of type java.util.List (or Set) and you either need to provide a setter, or initialize it with a mutable value, e.g. this will bind to the properties above
So, let's have a look at Kotlin docs: listOf returns a new read-only list of given elements. So the list is not mutable as required by the docs, which I assume is why it doesn't work. Try using a mutable list (since I have never used Kotlin, I cannot give you working code). Also try to declare it as java.util.List if that's possible in Kotlin.

Is there a built-in and concise way to define required properties for a given Gradle task?

I have a few custom Gradle tasks that require certain properties to be passed to the script as CLI arguments (using -P). For example:
task example(type: Copy) {
if (!hasProperty('foo')) {
throw new GradleException("The [foo] property is required")
}
if (!hasProperty('bar')) {
throw new GradleException("The [bar] property is required")
}
if (!hasProperty('baz')) {
throw new GradleException("The [baz] property is required")
}
}
Does Gradle provide a built-in and concise way to define such requirements?
There is no build-in possibility to do so without conditional checks.
But you can do it easily with net.saliman.properties plugin.
From the plugin documentation:
requiredProperty
requiredProperty "somePropertyName"
This method throws a MissingPropertyException if the named property is not defined.
requiredProperties
requiredProperties "property1", "property2", ...
This method throws a MissingPropertyException if any of the named properties are not defined
recommendedProperty
recommendedProperty "somePropertyName", "default File Text"
This method is handy when there are properties that have defaults somewhere else. For example, the build file might define it, or the application might be able to get it from a system file. It is most useful in alerting newer developers that something must be configured somewhere on their systems.
The method checks to see if the given property is defined. If it is not, a warning message is displayed alerting the user that a default will be used, and if the defaultFile has been given, the message will include it so that the developer knows which file will be providing the default value.
Your task will look like:
task example(type: Copy) {
requiredProperties "foo", "bar", "baz"
}

How can I provide custom logic in a Maven archetype?

I'm interested in creating a Maven archetype, and I think I have most of the basics down. However, one thing I'm stuck on is that sometimes I want to use custom logic to fill in a template. For example, if somebody generates my archetype and specifies the artifactId as hello-world, I'd like to generate a class named HelloWorld that simply prints out "Hello World!" to the console. If another person generates it with artifactId = howdy-there, the genned class would be HowdyThere and it would print out "Howdy There!".
I know that under the covers, Maven's archetype mechanism leverages the Velocity Template Engine, so I read this article on creating custom directives. This seemed to be what I was looking for, so I created a class called HyphenatedToCamelCaseDirective that extends org.apache.velocity.runtime.directive.Directive. In that class, my getName() implementation returns "hyphenatedCamelCase". In my archetype-metadata.xml file, I have the following...
<requiredProperties>
<requiredProperty key="userdirective">
<defaultValue>com.jlarge.HyphenatedToCamelCaseDirective</defaultValue>
</requiredProperty>
</requiredProperties>
My template class looks like this...
package ${package};
public class #hyphenatedToCamelCase('$artifactId') {
// userdirective = $userdirective
public static void main(String[] args) {
System.out.println("#hyphenatedToCamelCase('$artifactId')"));
}
}
After I install my archetype and then do an archetype:generate by specifying artifactId = howdy-there and groupId = f1.f2, the resulting class looks like this...
package f1.f2;
public class #hyphenatedToCamelCase('howdy-there') {
// userdirective = com.jlarge.HyphenatedToCamelCaseDirective
public static void main(String[] args) {
System.out.println("#hyphenatedToCamelCase('howdy-there')"));
}
}
The result shows that even though userdirective is being set the way I expected it to, It's not evaulating the #hyphenatedToCamelCase directives like I was hoping. In the directive class, I have the render method logging a message to System.out, but that message doesn't show up in the console, so that leads me to believe that the method never got executed during archetype:generate.
Am I missing something simple here, or is this approach just not the way to go?
The required properties section of the archetype-metatadata xml is used to pass additional properties to the velocity context, it is not meant for passing velocity engine configuration. So setting a property called userDirective will only make the variable $userDirective availble and not add a custom directive to the velocity engine.
If you see the source code, the velocity engine used by maven-archetype plugin does not depend on any external property source for its configuration. The code that generates the project relies on an autowired (by the plexus container) implementation of VelocityComponent.
This is the code where the velocity engine is initialized:
public void initialize()
throws InitializationException
{
engine = new VelocityEngine();
// avoid "unable to find resource 'VM_global_library.vm' in any resource loader."
engine.setProperty( "velocimacro.library", "" );
engine.setProperty( RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, this );
if ( properties != null )
{
for ( Enumeration e = properties.propertyNames(); e.hasMoreElements(); )
{
String key = e.nextElement().toString();
String value = properties.getProperty( key );
engine.setProperty( key, value );
getLogger().debug( "Setting property: " + key + " => '" + value + "'." );
}
}
try
{
engine.init();
}
catch ( Exception e )
{
throw new InitializationException( "Cannot start the velocity engine: ", e );
}
}
There is a hacky way of adding your custom directive. The properties you see above are read from the components.xml file in the plexus-velocity-1.1.8.jar. So open this file and add your configuration property
<component-set>
<components>
<component>
<role>org.codehaus.plexus.velocity.VelocityComponent</role>
<role-hint>default</role-hint>
<implementation>org.codehaus.plexus.velocity.DefaultVelocityComponent</implementation>
<configuration>
<properties>
<property>
<name>resource.loader</name>
<value>classpath,site</value>
</property>
...
<property>
<name>userdirective</name>
<value>com.jlarge.HyphenatedToCamelCaseDirective</value>
</property>
</properties>
</configuration>
</component>
</components>
</component-set>
Next add your custom directive class file to this jar and run archetype:generate.
As you see this is very fraglie and you will need to figure a way to distribute this hacked plexus-velocity jar. Depending on what you are planning to use this archetype for it might be worth the effort.

Resources