You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@camel.apache.org by "Claus Ibsen (Jira)" <ji...@apache.org> on 2025/04/20 08:58:00 UTC
[jira] [Resolved] (CAMEL-21912) camel-cxf - Caching of ToStringTypeConverter breaks CXF RS Rest service
[ https://issues.apache.org/jira/browse/CAMEL-21912?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]
Claus Ibsen resolved CAMEL-21912.
---------------------------------
Resolution: Fixed
> 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)