Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,42 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stax.StAXSource;
import javax.xml.transform.stream.StreamSource;

import org.w3c.dom.Document;

import org.apache.camel.NoTypeConversionAvailableException;
import org.apache.camel.StreamCache;
import org.apache.camel.TypeConversionException;
import org.apache.camel.TypeConverter;
import org.apache.camel.component.cxf.common.CxfPayload;
import org.apache.camel.language.xpath.XPathBuilder;
import org.apache.camel.spi.TypeConverterRegistry;
import org.apache.camel.test.junit6.ExchangeTestSupport;
import org.apache.cxf.staxutils.StaxSource;
import org.apache.cxf.staxutils.StaxUtils;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class CachedCxfPayloadTest extends ExchangeTestSupport {
private static final String PAYLOAD = "<foo>bar<![CDATA[ & a cdata section ]]></foo>";
private static final String PAYLOAD_AMPED = "<foo>bar &amp; a cdata section </foo>";
private static final String NS_PAYLOAD_NAMESPACE = "urn:camel:test";
private static final String NS_PAYLOAD = "<foo xmlns=\"" + NS_PAYLOAD_NAMESPACE + "\"><bar>baz</bar></foo>";

@Test
public void testCachedCxfPayloadSAXSource()
Expand Down Expand Up @@ -79,6 +94,56 @@ public void testCachedCxfPayloadStreamSource()
doTest(source, PAYLOAD);
}

@Test
public void testCachedCxfPayloadUsesTheStreamCacheConverters() {
TypeConverterRegistry registry = context.getTypeConverterRegistry();
// CAMEL-25231: a CachedCxfPayload is both a StreamCache and a CxfPayload, and both have converters to the XML
// sources. The interface is tried before the super class, so the StreamCache converters must always be chosen,
// whatever the order of the converters in the registry, which can change between JVM runs
for (Class<?> type : List.of(Source.class, SAXSource.class, DOMSource.class, StAXSource.class,
StreamSource.class, Document.class)) {
TypeConverter fromStreamCache = registry.lookup(type, StreamCache.class);
assertNotNull(fromStreamCache, "no converter from StreamCache to " + type.getName());
assertNotSame(registry.lookup(type, CxfPayload.class), fromStreamCache);
assertSame(fromStreamCache, registry.lookup(type, CachedCxfPayload.class), type.getName());
}
}

@Test
public void testCachedCxfPayloadToDOMSourceHoldsTheDocument() throws Exception {
// CAMEL-25231: the DOMSource must hold the document, not its document element, as the xslt, xquery and
// validator components then evaluate from another context node (see CAMEL-25224)
for (CachedCxfPayload<?> cache : List.of(cachedDomPayload(), cachedStaxPayload())) {
DOMSource source = context.getTypeConverter().mandatoryConvertTo(DOMSource.class, exchange, cache);
Document document = assertInstanceOf(Document.class, source.getNode());
assertEquals("foo", document.getDocumentElement().getLocalName());
}
}

@Test
public void testXPathOnCachedCxfPayload() throws Exception {
for (CachedCxfPayload<?> cache : List.of(cachedDomPayload(), cachedStaxPayload())) {
exchange.getIn().setBody(cache);
String value = XPathBuilder.xpath("/x:foo/x:bar").namespace("x", NS_PAYLOAD_NAMESPACE).stringResult()
.evaluate(exchange, String.class);
assertEquals("baz", value);
}
}

@SuppressWarnings({ "unchecked", "rawtypes" })
private CachedCxfPayload<?> cachedDomPayload() throws Exception {
List<Source> body = new ArrayList<>();
body.add(new DOMSource(StaxUtils.read(new StringReader(NS_PAYLOAD)).getDocumentElement()));
return new CachedCxfPayload(new CxfPayload(null, body, null), exchange);
}

@SuppressWarnings({ "unchecked", "rawtypes" })
private CachedCxfPayload<?> cachedStaxPayload() {
List<Source> body = new ArrayList<>();
body.add(new StaxSource(StaxUtils.createXMLStreamReader(new StreamSource(new StringReader(NS_PAYLOAD)))));
return new CachedCxfPayload(new CxfPayload(null, body, null), exchange);
}

@SuppressWarnings({ "unchecked", "rawtypes" })
private void doTest(Object source, String payload) throws IOException {
CxfPayload<?> originalPayload = context.getTypeConverter().convertTo(CxfPayload.class, source);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
package org.apache.camel.converter;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.camel.Exchange;
Expand Down Expand Up @@ -125,6 +126,63 @@ public void testEnumWithoutMatchingConstant() {
cause.getMessage());
}

@Test
public void testCachedLookupFromInterfaceAndSuperClassIsDeterministic() {
// CAMEL-25231: like CachedCxfPayload (a CxfPayload that implements StreamCache), both the interface and the super
// class have a type converter. The interface is nearer, so lookup and convertTo must both choose its converter,
// whatever the order the type converters were added in, and the cached lookup must keep that choice
for (boolean interfaceFirst : List.of(true, false)) {
AtomicInteger fromCacheCalls = new AtomicInteger();
AtomicInteger fromPayloadCalls = new AtomicInteger();
TypeConverter fromCache = new CountingFooConverter(fromCacheCalls);
TypeConverter fromPayload = new CountingFooConverter(fromPayloadCalls);

DefaultTypeConverter fresh = new DefaultTypeConverter(
new DefaultPackageScanClassResolver(), new DefaultInjector(new DefaultCamelContext()), true, false);
ServiceHelper.startService(fresh);
try {
if (interfaceFirst) {
fresh.addTypeConverter(Foo.class, Cache.class, fromCache);
fresh.addTypeConverter(Foo.class, Payload.class, fromPayload);
} else {
fresh.addTypeConverter(Foo.class, Payload.class, fromPayload);
fresh.addTypeConverter(Foo.class, Cache.class, fromCache);
}

assertSame(fromCache, fresh.lookup(Foo.class, CachedPayload.class));
assertNotNull(fresh.convertTo(Foo.class, new CachedPayload()));
assertEquals(1, fromCacheCalls.get());
assertEquals(0, fromPayloadCalls.get());
assertSame(fromCache, fresh.lookup(Foo.class, CachedPayload.class));
} finally {
ServiceHelper.stopService(fresh);
}
}
}

private interface Cache {
}

private static class Payload {
}

private static final class CachedPayload extends Payload implements Cache {
}

private static final class CountingFooConverter extends TypeConverterSupport {
private final AtomicInteger calls;

private CountingFooConverter(AtomicInteger calls) {
this.calls = calls;
}

@Override
public <T> T convertTo(Class<T> type, Exchange exchange, Object value) {
calls.incrementAndGet();
return type == Foo.class ? type.cast(new Foo()) : null;
}
}

@Test
public void testTryConvertToEnumTriesTheDirectTypeConverter() throws Exception {
// trying to convert to an enum must not let a direct type converter throw an exception that is only ignored
Expand Down