I have had no sucess trying to convert this to Kotlin and use it.
It works as Java, just not with my Kotlin (which I converted using IntelliJ Kotlin Plugin)
the problem appears to be this part
#PluginFactory
public static AnsiConsoleAppender createAppender(
I tried to add #JvmStatic and I get this error:
Unable to invoke factory method in class AnsiConsoleAppender for element AnsiConsoleAppender: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method AnsiConsoleAppender$Companion.AnsiConsoleAppender, parameter filter java.lang.reflect.InvocationTargetException
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.appender.AppenderLoggingException;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.PatternLayout;
import java.io.Serializable;
/**
* <h2>AnsiConsoleAppender</h2>
*
* <h3>notes</h3>
* <li>class name need not match the #Plugin name</li>
* <h3>more<h3/>
* <li>How to Create a Custom Appender in log4j2?</li>
*/
#Plugin(name="AnsiConsoleAppender", category="Core", elementType="appender", printObject=true)
public final class AnsiConsoleAppender extends AbstractAppender {
protected AnsiConsoleAppender(String name, Filter filter,
Layout<? extends Serializable> layout, final boolean ignoreExceptions) {
super(name, filter, layout, ignoreExceptions);
}
// The append method is where the appender does the work.
// Given a log event, you are free to do with it what you want.
// This example demonstrates:
// 1. Concurrency: this method may be called by multiple threads concurrently
// 2. How to use layouts
// 3. Error handling
//#Override
public void append(LogEvent event) {
try {
final byte[] bytes = getLayout().toByteArray(event);
// output code goes here
} catch (Exception ex) {
if (!ignoreExceptions()) throw new AppenderLoggingException(ex);
}
}
// Your custom appender needs to declare a factory method
// annotated with `#PluginFactory`. Log4j will parse the configuration
// and call this factory method to construct an appender instance with
// the configured attributes.
#PluginFactory
public static AnsiConsoleAppender createAppender(
#PluginAttribute("name") String name,
#PluginElement("Layout") Layout<? extends Serializable> layout,
#PluginElement("Filter") final Filter filter,
#PluginAttribute("otherAttribute") String otherAttribute) {
if (name == null) {
LOGGER.error("No name provided for AnsiConsoleAppenderImpl");
return null;
}
if (layout == null) {
layout = PatternLayout.createDefaultLayout();
}
return new AnsiConsoleAppender(name, filter, layout, true);
}
}
import org.apache.logging.log4j.core.AbstractLifeCycle
import org.apache.logging.log4j.core.Filter
import org.apache.logging.log4j.core.Layout
import org.apache.logging.log4j.core.LogEvent
import org.apache.logging.log4j.core.appender.AbstractAppender
import org.apache.logging.log4j.core.appender.AppenderLoggingException
import org.apache.logging.log4j.core.config.plugins.Plugin
import org.apache.logging.log4j.core.config.plugins.PluginAttribute
import org.apache.logging.log4j.core.config.plugins.PluginElement
import org.apache.logging.log4j.core.config.plugins.PluginFactory
import org.apache.logging.log4j.core.layout.PatternLayout
import java.io.Serializable
#Plugin(name = "AnsiConsoleAppender", category = "Core", elementType = "appender", printObject = true)
class AnsiConsoleAppender /*protected*/ constructor(
name: String, filter: Filter,
layout: Layout<out Serializable>, ignoreExceptions: Boolean
) : AbstractAppender(name, filter, layout, ignoreExceptions) {
// The append method is where the appender does the work.
// Given a log event, you are free to do with it what you want.
// This example demonstrates:
// 1. Concurrency: this method may be called by multiple threads concurrently
// 2. How to use layouts
// 3. Error handling
//#Override
override fun append(event: LogEvent) {
try {
val bytes = layout.toByteArray(event)
//AnsiColor.out(String(bytes), ColorMaps.ASTERIKSY, null, true)
} catch (ex: Exception) {
if (!ignoreExceptions()) throw AppenderLoggingException(ex)
}
}
companion object {
// Your custom appender needs to declare a factory method
// annotated with `#PluginFactory`. Log4j will parse the configuration
// and call this factory method to construct an appender instance with
// the configured attributes.
#JvmStatic
#PluginFactory
fun createAppender(
#PluginAttribute("name") name: String?,
#PluginElement("Layout") layout: Layout<out Serializable>?,
#PluginElement("Filter") filter: Filter,
#PluginAttribute("otherAttribute") otherAttribute: String
): AnsiConsoleAppender? {
val lay = layout ?: PatternLayout.createDefaultLayout()
if (name == null) {
AbstractLifeCycle.LOGGER.error("No name provided for AnsiConsoleAppenderImpl")
return null
}
return AnsiConsoleAppender(name, filter, lay, true)
}
}
}
what am I missing?
The exception says filter is null.
So it should be nullable type.
companion object {
#PluginFactory
#JvmStatic
fun createAppender(
#PluginAttribute("name") name: String,
#PluginElement("Layout") layout: Layout<out Serializable>,
#PluginElement("Filter") filter: Filter?,
#PluginAttribute("otherAttribute") otherAttribute: String?
): AnsiConsoleAppender {
In Kotlin factory methods are usually defined in a companion object of a class, e.g.:
class AnsiConsoleAppender : AbstractAppender() {
companion object {
// add JvmStatic annotation if you want to call this method from Java file
#JvmStatic
#PluginFactory
fun createAppender(/*params*/): AnsiConsoleAppender {
//...
}
}
}
More on companion object
Related
I'm trying to build routes for my service with Micronaut. Following this tutorial the example works fine.
This is my controller:
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.PathVariable
#Controller
class DemoController {
#Get
fun issue(
#PathVariable a: String): String {
return "Issue # $a"
}
}
And this is my route class:
import io.micronaut.context.ExecutionHandleLocator
import io.micronaut.web.router.DefaultRouteBuilder
import io.micronaut.web.router.RouteBuilder
import jakarta.inject.Inject
import jakarta.inject.Singleton
#Singleton
class MyRoutes(executionHandleLocator: ExecutionHandleLocator,
uriNamingStrategy: RouteBuilder.UriNamingStrategy) :
DefaultRouteBuilder(executionHandleLocator, uriNamingStrategy) {
#Inject
fun issuesRoutes(demoController: DemoController) {
GET("/issues/show/{number}", demoController, "issue", String::class.java)
}
}
Everything working fine so far.
The problem is that I have more than one parameter in the endpoint. For example:
#Controller
class DemoController {
#Get
fun issue(
#PathVariable a: String,
#PathVariable b: String
): String {
return "Issue # $a"
}
}
In MyRoutes class, on the function issuesRoutes, I need to set the parameterTypes for 2 params now, and I don't know how should I do it.
The documentation of RouteBuilder says as follow:
Route the specified URI template to the specified target.
The number of variables in the template should match the number of method arguments
Params:
uri – The URI
target – The target
method – The method
**parameterTypes – The parameter types for the target method**
Returns:The route
#Override
public UriRoute GET(String uri, Object target, String method, Class... parameterTypes) {
return buildRoute(HttpMethod.GET, uri, target.getClass(), method, parameterTypes);
}
How could I tell the method the types of my two string params (#PathVariables) in this kind of param the method is expecting (Class... parameterTypes).
The error I get with this configuration is:
Caused by: io.micronaut.web.router.exceptions.RoutingException: No such route: com.example.rest.DemoController.issue
Given a controller:
#Controller
class DemoController {
#Get
fun issue(#PathVariable a: String, #PathVariable b: String) = "Issue # a=$a b=$b"
}
The route would be:
#Singleton
class MyRoutes(executionHandleLocator: ExecutionHandleLocator,
uriNamingStrategy: RouteBuilder.UriNamingStrategy) :
DefaultRouteBuilder(executionHandleLocator, uriNamingStrategy) {
#Inject
fun issuesRoutes(demoController: DemoController) {
GET("/issues/show/{a}/{b}", // uri
demoController, // target
"issue", // method
String::class.java, String::class.java) //vararg parameterTypes:Class
}
}
When using a ParameterizedTest annotation with a source containing an "Result" type class, the type of the parameter is not the same as the argument.
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
import java.util.stream.Stream
class testTest() {
companion object {
#JvmStatic
fun streamOfArguments(): Stream<Arguments?>? {
return Stream.of(
Arguments.of(Result.success(true)) // Type Result<Boolean>
)
}
}
#ParameterizedTest
#MethodSource("streamOfArguments")
fun testArgumentParameter(
argumentFromStream: Result<Result<Boolean>> // Wrapped in an additional successful `Result`
) {
println(argumentFromStream::class)
argumentFromStream.onSuccess{
println(it)
}
}
}
I put my AWS Lambda behind API gateway, and now trying to make an end-to-end call.
import java.io.InputStream
import com.amazonaws.services.lambda.runtime.{Context, RequestHandler}
import com.fasterxml.jackson.databind
case class MyClass(a: String, b: String)
class MyHandler extends RequestHandler[InputStream, Boolean] {
val scalaMapper: databind.ObjectMapper = {
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
new ObjectMapper().registerModule(new DefaultScalaModule)
}
def handleRequest(input: InputStream, context: Context): Boolean = {
val myClass = scalaMapper.readValue(input, classOf[MyClass])
isValid(myClass)
}
It works when I test locally by providing the handler with a string, but when in a Lambda, the handler can't use the input stream. I'm getting the error
Endpoint response body before transformations: {
"errorMessage":"An error occurred during JSON parsing",
"errorType":"java.lang.RuntimeException",
"stackTrace":[],
"cause": {
"errorMessage":"com.fasterxml.jackson.databind.JsonMappingException:
Can not construct instance of java.io.InputStream,
problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information\n
at [Source: lambdainternal.util.NativeMemoryAsInputStream#2698dc7; line: 1, column: 1]",...
A Java Lambda has two possible signatures:
public interface RequestHandler<I, O> {
public O handleRequest(I input, Context context);
}
and
public interface RequestStreamHandler {
public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException;
}
I'm not sure if you're trying to mix the two but you're trying to tell Lambda to deserialize an InputStream.
While I try hard not to do anything in Scala, I believe you want:
case class MyClass(a: String, b: String)
class MyHandler extends RequestStreamHandler {
val scalaMapper: databind.ObjectMapper = {
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
new ObjectMapper().registerModule(new DefaultScalaModule)
}
def handleRequest(input: InputStream, output: OutputStream context: Context): Unit = {
val myClass = scalaMapper.readValue(input, classOf[MyClass])
output.write( isValid(myClass) )
}
Though I have not tested this. Another, perhaps better way would be:
case class MyClass(a: String, b: String)
class MyHandler extends RequestHandler[MyClass, Boolean] {
def handleRequest(myClass: MyClass, context: Context): Boolean = {
isValid(myClass)
}
I am trying to understand how to hide a base constructor parameter in a subclass in kotlin. How do you put a facade over a base constructor? This doesn't work:
import com.android.volley.Request
import com.android.volley.Response
class MyCustomRequest(url: String)
: Request<String>(Request.Method.POST, url, hiddenListener) {
private fun hiddenListener() = Response.ErrorListener {
/* super secret listener */
}
...
}
I think I understand the problem:
During construction of a new instance of a derived class, the base
class initialization is done as the first step (preceded only by
evaluation of the arguments for the base class constructor) and thus
happens before the initialization logic of the derived class is run.
I'm trying to solve this problem for Volley, where I need my custom request to be be a Request so that it can be passed into a RequestQueue. It would be easier of RequestQueue took in some kind of interface but since it doesn't I have to subclass. There are other ways I can hide these complexities from the caller, but this limitation has come up for me other times in Kotlin and I'm not sure how to solve it.
I am not familiar with volley but I tried to come up with an example that should give you some insight how to solve your problem. What you can do is use a companion object:
interface MyListener {
fun handleEvent()
}
open class Base<T>(anything: Any, val listener: MyListener) { // this would be your Request class
fun onSomeEvent() {
listener.handleEvent()
}
}
class Derived(anything: Any) : Base<Any>(anything, hiddenListener) { // this would be your MyCustomRequest class
private companion object {
private val hiddenListener = object : MyListener {
override fun handleEvent() {
// do secret stuff here
}
}
}
}
So if you apply this to your problem, the result should look something like this:
class MyCustomRequest(url: String)
: Request<String>(Request.Method.POST, url, hiddenListener) {
private companion object {
private val hiddenListener = Response.ErrorListener {
/* super secret listener */
}
}
...
}
A different way would be to use a decorator, create your Request withing that decorator and just delegate the calls to it:
class Decorator(anything: Any) {
private var inner: Base<Any>
private val hiddenListener: MyListener = object : MyListener {
override fun handleEvent() { }
}
init {
inner = Base(anything, hiddenListener)
}
}
And once again for your example that would look like this:
class MyCustomRequest(url: String) {
private var inner: Request<String>
private val hiddenListener = Response.ErrorListener {
/* super secret listener */
}
init {
inner = Request<String>(Request.Method.POST, url, hiddenListener)
}
...
}
I have an extension function for interface like the following:
import javax.jms.ConnectionFactory
fun ConnectionFactory.foo() {
println("do some stuff")
}
How can I mock the function foo?
Please note, I have seen approaches for classes and objects in http://mockk.io/#extension-functions, but it does not work. I have tried this one:
import io.mockk.classMockk
import io.mockk.every
import org.junit.Test
import javax.jms.ConnectionFactory
class ExtensionFunctionTest {
#Test
fun mockExtensionFunction() {
val connectionFactory = classMockk(ConnectionFactory::class)
every { connectionFactory.foo() } returns println("do other stuff")
connectionFactory.foo()
}
}
It throws exception:
io.mockk.MockKException: Missing calls inside every { ... } block.
According to the documentation in case of module wide extension functions you need to staticMock "hidden" class created for an extension function.
Here is an example (assuming the file name is com/sample/extmockingtest/SampleTest.kt):
fun <T> Iterable<T>.foo(): String = "do some stuff"
class ExtensionFunctionTest {
#Test
fun mockExtensionFunction() {
val itMock = classMockk(Iterable::class);
staticMockk("com.sample.extmockingtest.SampleTestKt").use {
every {
itMock.foo()
} returns "do other stuff"
assertEquals("do other stuff", itMock.foo())
verify {
itMock.foo()
}
}
}
}