[ https://issues.apache.org/jira/browse/CAMEL-21912?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]
Claus Ibsen updated CAMEL-21912: -------------------------------- Summary: camel-cxf - Caching of ToStringTypeConverter breaks CXF RS Rest service (was: Caching of ToStringTypeConverter breaks CXF RS Rest service) > camel-cxf - Caching of ToStringTypeConverter breaks CXF RS Rest service > ----------------------------------------------------------------------- > > Key: CAMEL-21912 > URL: https://issues.apache.org/jira/browse/CAMEL-21912 > Project: Camel > Issue Type: Bug > Components: camel-core, camel-cxf > Affects Versions: 4.2.0, 4.4.5, 4.8.5, 4.10.2 > Environment: Camel 4.2.0 (or later) - tested with 4.10.2 > Java 21 > Spring Boot 3.4.4 > CXF 4.1.1 > Reporter: Dave Riseley > Assignee: Claus Ibsen > Priority: Minor > Fix For: 4.8.7, 4.10.4, 4.12.0 > > Attachments: ArrayListConverterTests.java, pom.xml > > > We have encountered an issue where logging an ArrayList<T> in one camel route > can break a CXF JaxRS service in another route. > > After some considerable debugging, it appears that the logging of the form: > {{log(LoggingLevel.INFO, "Logging body: ${body}")}} > causes a {{ToStringTypeConverter}} to be cached for the {{ArrayList}} to > {{String type}} conversion. > This in turn causes JaxRS Rest service to break as the cxf > {{MessageContentsList}} to {{String}} conversion incorrectly uses the > {{ToStringTypeConverter}} instead of the {{SpringTypeConverter}} converter > A full test is shown below. I will attach the test and the pom.xml required > to build it. > In the test below a simple CxfRS echo service is created, where the response > is the value of the {{say}} query parameter. > For a call to http://localhost:8001/echo?say=hello we would expect the > response payload of {{"hello"}}. When bug is triggered (by logging an > arraylist in a separate route), the actual response payload returned is > {{"[hello]"}} (notice the additional square brackets) > In my opinion, logging shouldn't be able to cause the side effects seen here. > This behaviour appears to have been introduced in Camel 4.2.0 by either (or > both) of CAMEL-20051 and CAMEL-19398, and is present in the latest version > 4.10.2 > [~orpiske] FYI. > {code:java} > package com.example.arrayconverter; > import static org.junit.jupiter.api.Assertions.*; > import java.util.ArrayList; > import java.util.List; > import org.apache.camel.Exchange; > import org.apache.camel.LoggingLevel; > import org.apache.camel.ProducerTemplate; > import org.apache.camel.RoutesBuilder; > import org.apache.camel.TypeConverter; > import org.apache.camel.builder.RouteBuilder; > import > org.apache.camel.component.cxf.spring.jaxrs.SpringJAXRSServerFactoryBean; > import org.apache.camel.impl.converter.DefaultTypeConverter; > import org.apache.camel.test.spring.junit5.CamelSpringBootTest; > import org.apache.cxf.Bus; > import org.apache.cxf.message.MessageContentsList; > import org.junit.jupiter.api.Order; > import org.junit.jupiter.api.Test; > import org.springframework.beans.factory.annotation.Autowired; > import org.springframework.boot.autoconfigure.EnableAutoConfiguration; > import org.springframework.boot.test.context.SpringBootTest; > import org.springframework.context.annotation.Bean; > import org.springframework.context.annotation.Configuration; > import org.springframework.test.annotation.DirtiesContext; > import org.springframework.test.annotation.DirtiesContext.ClassMode; > import jakarta.ws.rs.*; > import jakarta.ws.rs.core.*; > @CamelSpringBootTest > @EnableAutoConfiguration > @SpringBootTest > @DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) > class ArrayListConverterTests { > @Autowired > DefaultTypeConverter defaultTypeConverter; > @Autowired > ProducerTemplate producerTemplate; > @Configuration > static class TestConfig { > @Bean > SpringJAXRSServerFactoryBean restServer(Bus bus) { > SpringJAXRSServerFactoryBean restServer = new > SpringJAXRSServerFactoryBean(); > restServer.setBus(bus); > restServer.setAddress("http://localhost:8001/echo"); > restServer.setServiceClass(EchoService.class); > return restServer; > } > @Bean > RoutesBuilder loggingRoute() { > return new RouteBuilder() { > @Override > public void configure() throws Exception { > from("direct:logging").log(LoggingLevel.INFO, "Logging > body: ${body}"); > } > }; > } > @Bean > RoutesBuilder echoService() { > return new RouteBuilder() { > @Override > public void configure() throws Exception { > from("cxfrs:bean:restServer").bean(new EchoServiceImpl()); > } > }; > } > } > private record Fruit(String name) { > } > private interface EchoService { > @GET > @Path("/") > @Produces(MediaType.TEXT_PLAIN) > public Response echo(@QueryParam("say") String say); > } > private static class EchoServiceImpl implements EchoService { > @Override > public Response echo(String say) { > return Response.ok().entity(say).build(); > } > } > @Test > @Order(1) > void testSuccessfulCxfRestCallWithoutCallingLoggingRoute() { > > // Call the echo CXF Rest service > Exchange responseExchange = > producerTemplate.send("http://localhost:8001/echo?say=hello", e -> {}); > assertNull(responseExchange.getException()); > assertEquals(200, > responseExchange.getMessage().getHeader(Exchange.HTTP_RESPONSE_CODE, > Integer.class)); > // This is the expected output - the value of the say query paramater > assertEquals("hello", > responseExchange.getMessage().getBody(String.class)); > // There is no List -> String type convertor > TypeConverter t = defaultTypeConverter.getTypeConverter(String.class, > ArrayList.class); > assertNull(t); > // The MessageContentsList -> String type coverter is the expected > SpringTypeConverter > t = defaultTypeConverter.getTypeConverter(String.class, > MessageContentsList.class); > assertNotNull(t); > assertEquals(org.apache.camel.spring.boot.SpringTypeConverter.class, > t.getClass()); > } > > @Test > @Order(2) > void testFailedCxfRestCallWithCallingLoggingRoute() { > // There is no List -> String type convertor > TypeConverter t = defaultTypeConverter.getTypeConverter(String.class, > ArrayList.class); > assertNull(t); > // Trigger logging of an array of records in one route > producerTemplate.sendBody("direct:logging", > new ArrayList<Fruit>( > List.of( > new Fruit("apples"), > new Fruit("bananas"), > new Fruit("carrots") > ))); > // Notice a List -> String type converter has been registered > t = defaultTypeConverter.getTypeConverter(String.class, > ArrayList.class); > assertNotNull(t); > > assertEquals(org.apache.camel.impl.converter.ToStringTypeConverter.class, > t.getClass()); > /// Call the echo CXF Rest service in separate route > Exchange responseExchange = > producerTemplate.send("http://localhost:8001/echo?say=hello", e -> {}); > assertNull(responseExchange.getException()); > assertEquals(200, > responseExchange.getMessage().getHeader(Exchange.HTTP_RESPONSE_CODE, > Integer.class)); > > // THIS IS THE FAILURE - the logging in the first route has impacted > the format of the payload > assertEquals("[hello]", > responseExchange.getMessage().getBody(String.class)); > // The type converter above overrides what should be the the > SpringTypeConverter > t = defaultTypeConverter.getTypeConverter(String.class, > MessageContentsList.class); > assertNotNull(t); > > assertEquals(org.apache.camel.impl.converter.ToStringTypeConverter.class, > t.getClass()); > } > } > {code} -- This message was sent by Atlassian Jira (v8.20.10#820010)