[
https://issues.apache.org/jira/browse/CAMEL-21912?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Claus Ibsen updated CAMEL-21912:
--------------------------------
Fix Version/s: 4.12.0
> 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
> Priority: Minor
> Fix For: 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)