The null safe operator in Groovy is great for reducing code and making things more readable. We can go from this:
def customer = getCustomer(custNo)
if(!customer)
throw new Exception("Invalid customer: ${custNo}")
def policy = customer.getPolicy(policyNo)
if(!policy)
throw new Exception("Invalid policy: ${policyNo}")
def claim = policy.getClaim(claimNo)
if(!claim)
throw new Exception("Invalid claim: ${claimNo}")
..to this...
def claim = getCustomer(custNo)?.getPolicy(policyNo)?.getClaim(claimNo)
But nothing's for free; using null/safe navigation, if claim is null, it's not immediately obvious what caused it: either custNo, policyNo, or claimNo may be invalid.
We could go back and start checking what's null, but that's counterproductive, and actually, it's not even possible since intermediate objects are not stored in variables.
So the question is: Is it possible to identify what was null when chaining method calls using null/safe navigation?
UPDATE
I took another stab at this using dynamic method invocation. It takes an init target (usually a dao) to initialize the object (customer in this case), and a map containing method names as strings (with arguments as values). Using an iterator, invokeChain simply traverses the map (chain); if anything in the chain returns null, identifying the method that caused it becomes trivial.
def invokeChain = { initTarget, chain ->
def obj
chain.eachWithIndex{ it, idx ->
//init obj from dao on first iteration only,
//remaining iterations get from obj itself
obj = (!idx) ? initTarget."$it.key"(it.value) : obj?."$it.key"(it.value)
if(!obj)
throw new Exception("${it.key}(${it.value}) returned null")
}
obj
}
Usage
Mock a customer dao for initTarget...I've inserted null as return type for getClaim(), which should throw an exception.
def static getCustomer = { custNo ->
[ getPolicy: { p ->
[getClaim:{ c ->
null //"Claim #${c}"
}]
}]
}
..using invokeChain, easy as pie:
def claim = invokeChain(this, [getCustomer:123, getPolicy:456, getClaim:789])
...throws exception, as expected:
Exception in thread "main" java.lang.Exception: getClaim(789) returned null
I like this approach because it's compact, readable, and easy to use; what do you think?
I think there is no clear way to do so.
I can be wrong, will check sources later, but safe navigation is a syntax sugar for if statement.
As a hack you can wrap your code with interceptor, trace last method call inside, and then use that info to proide error message.
It will not be cheap, and will cost you some code to realize interception and some performance while running. But you can achieve something like
mayFail("getCusomer", "getPolicy", "getClaim") {
getCustomer(custNo)?.getPolicy(policyNo)?.getClaim(claimNo)
} == "getPolicy" // failed on second step
EDIT: As #tim_yates proved, ?. is a syntax sugar with if construction behind. Thanks Vorg van Geir for the link, I have copied it here, to an answer. He say, it's outdated, and it looks like he is right. I have managed to make ProxyMetaClass work(in Groovy 2.0.6), so given way isn't totally broken. Now I need to specify exact classes to intercept, and I can not find a way to catch inherited method calls.(to simply intercept java.lang.Object)
def logInterceptor = new TracingInterceptor()
logInterceptor.writer = new StringWriter()
def intProxy = ProxyMetaClass.getInstance(Integer)
def stringProxy = ProxyMetaClass.getInstance(String)
intProxy.setInterceptor(logInterceptor)
stringProxy.setInterceptor(logInterceptor)
intProxy.use {
stringProxy.use {
println(("Hello" + "world").size().hashCode())
} }
println(logInterceptor.writer.toString())
All that hell may be wrapped in some utility code, but I highly doubt in necessarity of this. Performance overhead will be awful and some boilerplate code will remain.
The game is not worth the candle.
What about attributions and asserts?
def policy = customer?.policy
def claim = policy?.claim
def number = claim?.number
assert customer, "Invalid customer"
assert policy, 'Invalid policy'
assert claim, 'Invalid claim'
UPDATE:
You already found the solution, but i'd like to contribute with an interceptor idea:
The mock:
def dao = [
getCustomer : { custNo ->
[ getPolicy: { p ->
[getClaim:{ c ->
null //"Claim #${c}"
}]
}]
}
]
The interceptor:
class MethodCallInterceptor {
def delegate
def invokeMethod(String method, args) {
def result = delegate.invokeMethod(method, args)
if (result == null) {
throw new RuntimeException("$method returned null")
}
else {
new MethodCallInterceptor(delegate: result)
}
}
def getProperty(String property ) {
delegate[ property ]
}
void setProperty(String property, value) {
delegate[ property ] = value
}
}
The test:
def interceptedDao = new MethodCallInterceptor(delegate: dao)
try {
interceptedDao.getCustomer(123).getPolicy(456).getClaim(789)
assert false
} catch (e) {
assert e.message.contains( 'getClaim returned null' )
}
Related
I created some mixin methods. Code and example below:
URL.metaClass.withCreds = { u, p ->
delegate.openConnection().tap {
setRequestProperty('Authorization', "Basic ${(u + ':' + p).bytes.encodeBase64()}")
}
}
URLConnection.metaClass.fetchJson = {
delegate.setRequestProperty('Accept', 'application/json')
delegate.connect()
def code = delegate.responseCode
def result = new JsonSlurper().parse(code >= 400 ? delegate.errorStream : delegate.inputStream as InputStream)
[
ok : code in (200..299),
body: result,
code: code
]
}
example usage:
new URL("$baseUrl/projects/$name").withCreds(u, p).fetchJson().find {
it.ok
}?.tap{
it.repos = getRepos(it.key).collectEntries { [(it.slug): it] }
}
}
When I dont use find(), my object is, as expected, a map with those 3 elements. When I use find it is a Map.Entry with key ok and value true
which produces this error:
groovy.lang.MissingPropertyException: No such property: ok for class: java.util.LinkedHashMap$Entry
Possible solutions: key
It occured to me when I wrote this post that it was treated the map as an iterable and thus looking at every entry which I have subsequently verified. How do I find on the whole map? I want it.ok because if it's true, I need to carry it forward
There is no such method in Groovy SDK. Map.find() runs over an entry set of the map you call method on. Based on expectation you have defined I'm guessing you are looking for a function that tests map with a given predicate and returns the map if it matches the predicate. You may add a function that does to through Map.metaClass (since you already add methods to URL and URLConnection classes). Consider following example:
Map.metaClass.continueIf = { Closure<Boolean> predicate ->
predicate(delegate) ? delegate : null
}
def map = [
ok : true,
body: '{"message": "ok"}',
code: 200
]
map.continueIf { it.ok }?.tap {
it.repos = "something"
}
println map
In this example we introduced a new method Map.continueIf(predicate) that tests if map matches given predicate and returns a null otherwise. Running above example produces following output:
[ok:true, body:{"message": "ok"}, code:200, repos:something]
If predicate is not met, map does not get modified.
Alternatively, for more strict design, you could make fetchJson() method returning an object with corresponding onSuccess() and onError() methods so you can express more clearly that you add repos when you get a successful response and optionally you create an error response otherwise.
I hope it helps.
How come I cannot declare an array of People in Groovy as shown.
Maybe I'm lacking the deeper understanding of classes
class People {
Integer id
}
class Job {
def func() {
People[] p = new People[10]
}
}
I get an error of People[] cannot be applied to app.People[]
The code sample you have shown does not reproduce the error you mentioned in the question above. It's broken actually and does not compile - method func() is missing its body. If you correct the code to e.g.
class People {
Integer id
}
class Job {
def func() {
People[] p = new People[10]
assert p.size() == 10
println p
}
}
new Job().func()
you will see it produces the expected result - check it out in the Groovy web console here. When you run it you will see following output to the console:
[null, null, null, null, null, null, null, null, null, null]
The difference between Groovy and Java
When it comes to array initialization there is one significant difference between Groovy and Java. In Java you can initialize an array of People[] like this:
People[] p = new People[] { new People(), new People(), /* ... */ new People() };
It wont work in Groovy, because Groovy reserves {} for closures. In Groovy you can initialize such array as:
People[] p = [new People(), new People(), new People()] as People[]
While Szymon Stepniak's answer is correct for Groovy 2.5 and below, Java-style array initialization are part of the enhancements of Groovy 3.0 and 2.6 made possible by the new parrot parser.
Example from the release notes:
def primes = new int[] {2, 3, 5, 7, 11}
assert primes.size() == 5 && primes.sum() == 28
assert primes.class.name == '[I'
def pets = new String[] {'cat', 'dog'}
assert pets.size() == 2 && pets.sum() == 'catdog'
assert pets.class.name == '[Ljava.lang.String;'
// traditional Groovy alternative still supported
String[] groovyBooks = [ 'Groovy in Action', 'Making Java Groovy' ]
assert groovyBooks.every{ it.contains('Groovy') }
Szymon Stepniak's answer is correct. I'll point another example of a real case in some unit test that I've worked (general Object type):
Object[] o = [YourModel] as Object[]
This is sufficient to mock a general Object with your model properties.
I have more than one endpoints.I am able to apply common filters on endpoints using finagle filter.But now I want to apply a filter on a specific endpoint.
How can I achieve this?
I had a similar question (for basic authentication filtering) that popped up while playing with redbubble's finch template which I partially solved in the following way:
class AuthenticatedEndpoint[A](e: Endpoint[A]) extends Endpoint[A] { self =>
final def apply(mapper: Mapper[A]): Endpoint[mapper.Out] = mapper(self)
final def apply(input: Input): Endpoint.Result[A] =
if (checkSession(input.request)) {
e(input)
} else {
// TODO return something meaningful to the caller (if possible?)
EndpointResult.Skipped
}
}
object AuthenticatedEndpoint {
def validSession[A](e: Endpoint[A]): Endpoint[A] = new AuthenticatedEndpoint(e)
}
(with checkSession returning true if all is well with the request). Then my api is defined as:
val api = "v1" :: loginApi :+: validSession(peopleApi :+: healthApi :+: adminApi)
This works well in the sense that requests without a session won't have access to the endpoints passed to validSession, but I have yet to find an easy way to return an error message to the caller, and I'd be curious to know if I chose the right path here.
This is how I got around it. It's probably not ideal but works.
class AuthenticatedEndpoint[A](e: Endpoint[A])(implicit auth: Request => Boolean) extends Endpoint[A] { self =>
final def apply(mapper: Mapper[A]): Endpoint[mapper.Out] = mapper(self)
final def apply(input: Input): Endpoint.Result[A] =
if (auth(input.request)) {
e(input)
} else {
EndpointResult.Matched[Nothing](input, Rerunnable( Unauthorized(new Exception(s"Authentication Failed."))) )
}
}
object AuthenticatedEndpoint {
def validSession[A](e: Endpoint[A]): Endpoint[A] = new AuthenticatedEndpoint(e)
}
I would like to write a system groovy script which inspects the queued jobs in Jenkins, and extracts the build parameters (and build cause as a bonus) supplied as the job was scheduled. Ideas?
Specifically:
def q = Jenkins.instance.queue
q.items.each { println it.task.name }
retrieves the queued items. I can't for the life of me figure out where the build parameters live.
The closest I am getting is this:
def q = Jenkins.instance.queue
q.items.each {
println("${it.task.name}:")
it.task.properties.each { key, val ->
println(" ${key}=${val}")
}
}
This gets me this:
4.1.next-build-launcher:
com.sonyericsson.jenkins.plugins.bfa.model.ScannerJobProperty$ScannerJobPropertyDescriptor#b299407=com.sonyericsson.jenkins.plugins.bfa.model.ScannerJobProperty#5e04bfd7
com.chikli.hudson.plugin.naginator.NaginatorOptOutProperty$DescriptorImpl#40d04eaa=com.chikli.hudson.plugin.naginator.NaginatorOptOutProperty#16b308db
hudson.model.ParametersDefinitionProperty$DescriptorImpl#b744c43=hudson.mod el.ParametersDefinitionProperty#440a6d81
...
The params property of the queue element itself contains a string with the parameters in a property file format -- key=value with multiple parameters separated by newlines.
def q = Jenkins.instance.queue
q.items.each {
println("${it.task.name}:")
println("Parameters: ${it.params}")
}
yields:
dbacher params:
Parameters:
MyParameter=Hello world
BoolParameter=true
I'm no Groovy expert, but when exploring the Jenkins scripting interface, I've found the following functions to be very helpful:
def showProps(inst, prefix="Properties:") {
println prefix
for (prop in inst.properties) {
def pc = ""
if (prop.value != null) {
pc = prop.value.class
}
println(" $prop.key : $prop.value ($pc)")
}
}
def showMethods(inst, prefix="Methods:") {
println prefix
inst.metaClass.methods.name.unique().each {
println " $it"
}
}
The showProps function reveals that the queue element has another property named causes that you'll need to do some more decoding on:
causes : [hudson.model.Cause$UserIdCause#56af8f1c] (class java.util.Collections$UnmodifiableRandomAccessList)
I have a scenario where users are assigned to team.
Different ClientServices are allocated to different teams and
we need to assign user Of these teams to clientservice in RoundRobin fashion
I was trying to solve it as follows to get a map where team name and a list of ClientServiceInstance will be mapped so I can do further processing on it
def teamMap = [:]
clientServicesList.each {clientServiceInstance->
if(teamMap[clientServiceInstance.ownerTeam] == null){
teamMap.putAt(clientServiceInstance.ownerTeam, new ArrayList().push(clientServiceInstance))
}else{
def tmpList = teamMap[clientServiceInstance.ownerTeam]
tmpList.push(clientServiceInstance)
teamMap[clientServiceInstance.ownerTeam] = tmpList
}
}
but instead of pushing clientServiceInstance it pushes true.
Any idea?
I believe another version would be:
def teamMap = clientServicesList.inject( [:].withDefault { [] } ) { map, instance ->
map[ instance.ownerTeam ] << instance
map
}
new ArrayList().push(clientServiceInstance) returns true, which means you're putting that into your teamMap instead of what I assume should be a list? Instead you might want
teamMap.putAt(clientServiceInstance.ownerTeam, [clientServiceInstance])
By the way, your code is not very Groovy ;)
You could rewrite it as something like
def teamMap = [:]
clientServicesList.each { clientServiceInstance ->
if (teamMap[clientServiceInstance.ownerTeam]) {
teamMap[clientServiceInstance.ownerTeam] << clientServiceInstance
} else {
teamMap[clientServiceInstance.ownerTeam] = [clientServiceInstance]
}
}
Although I'm sure there are even better ways to write that.