I have the following groovy class as part of my gradle plugin:
class MyClass {
final Expando someOptions
MyClass() {
someOptions = new Expando()
}
def call(Closure configure) {
configure.delegate = someOptions
configure.resolveStrategy = Closure.DELEGATE_ONLY
configure()
}
}
Now I want to user to have the ability to configure this class by adding extra properties to it, but those properties should be stored in someOptions.
I tried doing this in the class:
def call(final Closure configure) {
configure.delegate = someOptions
configure.resolveStrategy = Closure.DELEGATE_ONLY
configure()
}
The user of the plugin can do:
myClass {
hello='world'
}
However, gradle does not seem to understand that the hello property does not exist on the myClass instance but rather on someOptions within the class. Whenever I use the above, I get errors about hello not existing in the MyClass instance.
How do I do this? Is it possible?
FWIW, it works in the groovy console, but not in gradle.
Any classes you define in your plugin are not directly used in Gradle, but wrapped in proxy classes by Gradle. As an example,
Gradle will create a proxy class for the actual class implementation and adds (among other things) also a property setter method. The method has the name of the property and has a single argument of the same type as the property. It is different from the setProperty and getProperty methods already added by Groovy. For example if we have a task with a property with the name message of type String then Gradle will add the method message(String) to the proxy class. (Source)
This is the reason, why you can omit the assignment sign in Gradle scrips:
task myTask {
myProperty true // uses Gradle generated method
myProperty = true // uses Groovy generated setter
}
Gradle also adds a method similar to yours to allow the configuration of any object in the DSL:
myExtension {
// this works thanks to Gradle
}
Without this proxy method, it would be necessary to use the method with(Closure) from the Groovy language for any block:
myExtension.with {
// this works thanks to Groovy
}
It seems like this proxy method overrides the call(Closure) method of your example.
To solve this, you could use the Delegate annotation in Groovy on someOptions. This would make all its properties available to the MyClass instance. You could also register someOptions as convention on MyClass.
EDIT
You can see that your method is never called by comparing the stacktrace of your current example and a second stacktrace, after you changed the name of the call method and called it explicitly (you need to use another property to get the same exception).
Related
I've got a custom task (specifically https://github.com/marklogic-community/ml-gradle/blob/master/src/main/groovy/com/marklogic/gradle/task/MlcpTask.groovy#L26 , though I'm going to provide a bare minimum example below) where I've been using #Delegate on a task property for many versions of Gradle, but it no longer works with Gradle 7. I've tried adding #Input/#Optional everywhere, but no luck.
My use case in the above task is that I want to reuse a Java class that has dozens of properties on it, and I want a user to be able to assign values to those properties via task properties in their build.gradle file.
Here's the easiest way to reproduce the problem I'm having - I add the following to my build.gradle file:
class MyTask extends DefaultTask {
#Delegate
MyBean myBean = new MyBean()
#TaskAction
void myAction() {
println "myBean value: " + myBean.myValue
}
}
class MyBean {
String myValue
}
task myTest(type: MyTask) {
myValue = "something"
}
And when I run "myTest" using Gradle 6.9, it runs fine and prints out "myBean value: something". When I run this on Gradle 7.1.1, I get an error of "Type 'MyTask' property 'myBean' is missing an input or output annotation.".
So I added #Input to myBean. Then I get an error of "Type 'MyTask' property 'myValue' is missing an input or output annotation."
So I add #Input to myValue in the MyBean class, and I get the same error of "Type 'MyTask' property 'myValue' is missing an input or output annotation."
Is there a way for #Delegate to still work in this fashion in Gradle, and if so, how? Or is this possibly a bug in Gradle 7? I'll file one if so. Or, is there a better way to do this - i.e. to reuse an existing Java class in a Gradle task such that the task can delegate property assignments to the Java class?
I want to initialise a member variable from a value I pick from ENV but it is not available in init block as it gets picked up after object initialisation
private lateinit var needValueHere: String
#Value("\${CLIENT_ID:NA}")
private val CLIENT_ID: String = ""
init {
this.needValueHere = this.CLIENT_ID
}
This is a simplified version of the actual problem.
I have verified the value is available in the member functions.
Your object is constructing by the following way:
Create object (e.g. call constructor)
Via reflection: put dependencies (e.g. fill values under #Autowired, #Value and other annotations).
Your init block is part of constructor, e.g. all Spring-related items aren't initialized here.
How you can fix this:
Extract properties to the type-safe configuration (please see official docs here)
Use notation of class like below.
Create private lateinit var field and don't call it until Spring initialization finishing (this is useful for integration tests, e.g. test methods start only after full warmup). Another option - use kotlin lazy notation. However whole this item couldn't be named as "good code".
class MyService(#Value("\${CLIENT_ID:NA}") private val needValueHere: String) {
/* */
}
I am confused with Groovy method visibility in the context of my Gradle build.
For some tests in my project, I have to first start a server.
For this I created a custom task class that extends Gradle's Test like so:
class TestWithServer extends Test {
TestWithServer() {
super()
beforeTest {
startServer()
}
}
private void startServer() {
println('placeholder')
}
}
But if I try to run such a task, I get an error:
Could not find method startServer() for arguments [] on task ':testWithServer' of type TestWithServer.
I found that when I change the visibility of startServer() to the default (public), the task runs fine.
How come I can't use the private method from within its own class?
It is not the same class, because Gradle adds some magic to the task types. Just add println this.class into the beforeTest closure to see the name of the actual class (something like TestWithServer_Decorated). This additional magic also explains why the error message contains the task name and how the class knows about being a task (type) at all. Since the decorated class is a subclass of your class you can use the protected modifier to encapsulate your method.
We use a abstract classes for services like this pseudocode
abstract class AbstractApiService {
#Timed(value="get", useClassPrefix=true)
def get(Long id) {
... returns sth ....
}
#Timed(value="create", useClassPrefix=true)
def create(Map params) {
... returns sth ....
}
}
There are beans which inherit AbstractApiService and serve features like creating, deleting, updating entities like
class UserAccountService extends AbstractApiService {
... code ....
}
I would like to get metrics for each call a function from child classes like UserAccountService, but Prometheus sends events with full parent class prefix.
App is based on Grails 3.3.8
build.gradle:
compile 'com.moelholm:prometheus-spring-boot-starter:1.0.2'
compile 'io.dropwizard.metrics:metrics-core:4.0.0-alpha2'
compile 'io.dropwizard.metrics:metrics-jvm:4.0.0-alpha2'
compile 'org.grails.plugins:dropwizard-metrics:1.0.0.M2'
Unfortunately, I believe you will have to define the #Timed annotation for each method you want tracked. I don't think that the annotation code will spin up a separate metric for each concrete class.
I created a custom manual solution
https://gist.github.com/michmzr/1e03534bc5fb6df89065f6964acf9c71
I have an Xtext project, and i would like to use an external properties file to be used in validation..
e.g. for the Hello world! project, and the following properties file...
hello.properties:
name=world
...create a validation rule that checks for Hello world! that world is the value of name in the properties file.
I would like the properties to only be read in once, such as when eclipse loads rather than every time the validation method is run as I am guessing this will be very slow. Where can I read them in so that this is the case?
Thanks, Sean
You may want to provide a class that allows to retrieve the values from the properties file. This class should be marked as #Singleton and clients of that implementation have to obtain the only instance via dependency injection.
#Singleton
public class MyPropertiesAccess {
private Properties properties;
public Properties getProperties() {
if (properties == null) {
properties = ...load...
}
return properties;
}
}
public class MyDslValidator {
#Inkect MyPropertiesAccess propertiesAccess;
}