Skip to content

Commit

Permalink
Merge pull request #94 from AxonIQ/fix/allow-nulling-decorators
Browse files Browse the repository at this point in the history
Support decorators with null fields of type
  • Loading branch information
CodeDrivenMitch authored Oct 22, 2024
2 parents 24b236f + 49d1785 commit eab5a41
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,19 @@ import java.time.Duration
* Useful for when users wrap components as decorators, like Axon FireStarter does.
*/
fun <T : Any> T.unwrapPossiblyDecoratedClass(clazz: Class<out T>): T {
return fieldOfMatchingType(clazz)
?.let { ReflectionUtils.getFieldValue(it, this) as T }
?.unwrapPossiblyDecoratedClass(clazz)
// No field of provided type - reached end of decorator chain
?: this
return fieldsOfMatchingType(clazz)
.mapNotNull { ReflectionUtils.getFieldValue(it, this) as T? }
.map { it.unwrapPossiblyDecoratedClass(clazz) }
.firstOrNull()
// No field of provided type - reached end of decorator chain
?: this
}

private fun <T : Any> T.fieldOfMatchingType(clazz: Class<out T>): Field? {
private fun <T : Any> T.fieldsOfMatchingType(clazz: Class<out T>): List<Field> {
// When we reach our own AS-classes, stop unwrapping
if (this::class.java.name.startsWith("org.axonframework") && this::class.java.simpleName.startsWith("AxonServer")) return null
if (this::class.java.name.startsWith("org.axonframework") && this::class.java.simpleName.startsWith("AxonServer")) return listOf()
return ReflectionUtils.fieldsOf(this::class.java)
.firstOrNull { f -> clazz.isAssignableFrom(f.type) }
.filter { f -> clazz.isAssignableFrom(f.type) }
}

fun <K, V> MutableMap<K, V>.computeIfAbsentWithRetry(key: K, retries: Int = 0, defaultValue: (K) -> V): V {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright (c) 2024. AxonIQ B.V.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.axoniq.console.framework

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test

class UtilsKtTest {
@Test
fun `Unwraps decorator with single field`() {
val core = MyCoreDecoratableService()
val decorated = MySingleValueDecoratableService(core)
val unwrapped = decorated.unwrapPossiblyDecoratedClass(MyDecoratableService::class.java)
assertEquals(core, unwrapped)
}

@Test
fun `Unwraps decorator that has a nullable field out of two`() {
val core = MyCoreDecoratableService()
val decorated = MyDoubleNullableValueDecoratableService(core, null)
val unwrapped = decorated.unwrapPossiblyDecoratedClass(MyDecoratableService::class.java)
val decorated2 = MyDoubleNullableValueDecoratableService(null, core)
val unwrapped2 = decorated2.unwrapPossiblyDecoratedClass(MyDecoratableService::class.java)
assertEquals(core, unwrapped)
assertEquals(core, unwrapped2)
}

@Test
fun `Does not crash on null fields of single-field decorator and returns decorator itself`() {
val decorated = MySingleValueDecoratableService(null)
val unwrapped = decorated.unwrapPossiblyDecoratedClass(MyDecoratableService::class.java)
assertEquals(decorated, unwrapped)
}

@Test
fun `Does not crash on both null fields of double-field decorator and returns decorator itself`() {
val decorated = MyDoubleNullableValueDecoratableService(null, null)
val unwrapped = decorated.unwrapPossiblyDecoratedClass(MyDecoratableService::class.java)
assertEquals(decorated, unwrapped)
}

interface MyDecoratableService {

}

class MyCoreDecoratableService : MyDecoratableService {

}

class MySingleValueDecoratableService(
val delegate: MyDecoratableService?
) : MyDecoratableService


class MyDoubleNullableValueDecoratableService(
val delegate: MyDecoratableService?,
val delegate2: MyDecoratableService?,
) : MyDecoratableService
}

0 comments on commit eab5a41

Please sign in to comment.