[ 
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)

Reply via email to