Author: davsclaus Date: Fri Feb 27 08:53:05 2009 New Revision: 748436 URL: http://svn.apache.org/viewvc?rev=748436&view=rev Log: CAMEL-1401: Fixed thread safe issue with JAXBDataFormat.
Added: camel/trunk/components/camel-jaxb/src/test/java/org/apache/camel/example/DataFormatConcurrentTest.java (with props) camel/trunk/components/camel-jaxb/src/test/java/org/apache/camel/example/DataFormatDataSetTest.java (contents, props changed) - copied, changed from r748394, camel/trunk/components/camel-jaxb/src/test/java/org/apache/camel/example/DataFormatTest.java Modified: camel/trunk/components/camel-jaxb/src/main/java/org/apache/camel/converter/jaxb/FallbackTypeConverter.java camel/trunk/components/camel-jaxb/src/main/java/org/apache/camel/converter/jaxb/JaxbConverter.java camel/trunk/components/camel-jaxb/src/main/java/org/apache/camel/converter/jaxb/JaxbDataFormat.java camel/trunk/components/camel-jaxb/src/test/java/org/apache/camel/example/JAXBConvertTest.java Modified: camel/trunk/components/camel-jaxb/src/main/java/org/apache/camel/converter/jaxb/FallbackTypeConverter.java URL: http://svn.apache.org/viewvc/camel/trunk/components/camel-jaxb/src/main/java/org/apache/camel/converter/jaxb/FallbackTypeConverter.java?rev=748436&r1=748435&r2=748436&view=diff ============================================================================== --- camel/trunk/components/camel-jaxb/src/main/java/org/apache/camel/converter/jaxb/FallbackTypeConverter.java (original) +++ camel/trunk/components/camel-jaxb/src/main/java/org/apache/camel/converter/jaxb/FallbackTypeConverter.java Fri Feb 27 08:53:05 2009 @@ -21,6 +21,8 @@ import java.io.Reader; import java.io.StringReader; import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; @@ -44,6 +46,7 @@ */ public class FallbackTypeConverter implements TypeConverter, TypeConverterAware { private static final transient Log LOG = LogFactory.getLog(FallbackTypeConverter.class); + private Map<Class, JAXBContext> contexts = new HashMap<Class, JAXBContext>(); private TypeConverter parentTypeConverter; private boolean prettyPrint = true; @@ -85,15 +88,19 @@ protected <T> boolean isJaxbType(Class<T> type) { XmlRootElement element = type.getAnnotation(XmlRootElement.class); - boolean jaxbType = element != null; - return jaxbType; + return element != null; } /** * Lets try parse via JAXB */ protected <T> T unmarshall(Class<T> type, Object value) throws JAXBException { + if (value == null) { + throw new IllegalArgumentException("Cannot convert from null value to JAXBSource"); + } + JAXBContext context = createContext(type); + // must create a new instance of unmarshaller as its not thred safe Unmarshaller unmarshaller = context.createUnmarshaller(); if (parentTypeConverter != null) { @@ -136,6 +143,7 @@ } catch (NoTypeConversionAvailableException e) { // lets try a stream StringWriter buffer = new StringWriter(); + // must create a new instance of marshaller as its not thred safe Marshaller marshaller = context.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, isPrettyPrint() ? Boolean.TRUE : Boolean.FALSE); marshaller.marshal(value, buffer); @@ -172,8 +180,13 @@ return null; } - protected <T> JAXBContext createContext(Class<T> type) throws JAXBException { - JAXBContext context = JAXBContext.newInstance(type); + protected synchronized <T> JAXBContext createContext(Class<T> type) throws JAXBException { + JAXBContext context = contexts.get(type); + if (context == null) { + context = JAXBContext.newInstance(type); + contexts.put(type, context); + } return context; } + } Modified: camel/trunk/components/camel-jaxb/src/main/java/org/apache/camel/converter/jaxb/JaxbConverter.java URL: http://svn.apache.org/viewvc/camel/trunk/components/camel-jaxb/src/main/java/org/apache/camel/converter/jaxb/JaxbConverter.java?rev=748436&r1=748435&r2=748436&view=diff ============================================================================== --- camel/trunk/components/camel-jaxb/src/main/java/org/apache/camel/converter/jaxb/JaxbConverter.java (original) +++ camel/trunk/components/camel-jaxb/src/main/java/org/apache/camel/converter/jaxb/JaxbConverter.java Fri Feb 27 08:53:05 2009 @@ -25,44 +25,39 @@ import javax.xml.bind.util.JAXBSource; import javax.xml.parsers.ParserConfigurationException; -import org.w3c.dom.Document; - import org.apache.camel.Converter; import org.apache.camel.Exchange; import org.apache.camel.Message; import org.apache.camel.converter.HasAnnotation; import org.apache.camel.converter.jaxp.XmlConverter; +import org.w3c.dom.Document; /** * @version $Revision$ */ public final class JaxbConverter { - private XmlConverter jaxbConverter; + private XmlConverter xmlConverter = new XmlConverter(); private Map<Class, JAXBContext> contexts = new HashMap<Class, JAXBContext>(); - public XmlConverter getJaxbConverter() { - if (jaxbConverter == null) { - jaxbConverter = new XmlConverter(); - } - return jaxbConverter; - } - - public void setJaxbConverter(XmlConverter jaxbConverter) { - this.jaxbConverter = jaxbConverter; - } - @Converter public JAXBSource toSource(@HasAnnotation(XmlRootElement.class)Object value) throws JAXBException { + if (value == null) { + throw new IllegalArgumentException("Cannot convert from null value to JAXBSource"); + } JAXBContext context = getJaxbContext(value); return new JAXBSource(context, value); } @Converter public Document toDocument(@HasAnnotation(XmlRootElement.class)Object value) throws JAXBException, ParserConfigurationException { + if (value == null) { + throw new IllegalArgumentException("Cannot convert from null value to JAXBSource"); + } JAXBContext context = getJaxbContext(value); + // must create a new instance of marshaller as its not thred safe Marshaller marshaller = context.createMarshaller(); - Document doc = getJaxbConverter().createDocument(); + Document doc = xmlConverter.createDocument(); marshaller.marshal(value, doc); return doc; } @@ -80,19 +75,13 @@ } private synchronized JAXBContext getJaxbContext(Object value) throws JAXBException { - JAXBContext context = contexts.get(value.getClass()); + Class type = value.getClass(); + JAXBContext context = contexts.get(type); if (context == null) { - context = createJaxbContext(value); - contexts.put(value.getClass(), context); + context = JAXBContext.newInstance(type); + contexts.put(type, context); } return context; } - private JAXBContext createJaxbContext(Object value) throws JAXBException { - if (value == null) { - throw new IllegalArgumentException("Cannot convert from null value to JAXBSource"); - } - return JAXBContext.newInstance(value.getClass()); - } - } Modified: camel/trunk/components/camel-jaxb/src/main/java/org/apache/camel/converter/jaxb/JaxbDataFormat.java URL: http://svn.apache.org/viewvc/camel/trunk/components/camel-jaxb/src/main/java/org/apache/camel/converter/jaxb/JaxbDataFormat.java?rev=748436&r1=748435&r2=748436&view=diff ============================================================================== --- camel/trunk/components/camel-jaxb/src/main/java/org/apache/camel/converter/jaxb/JaxbDataFormat.java (original) +++ camel/trunk/components/camel-jaxb/src/main/java/org/apache/camel/converter/jaxb/JaxbDataFormat.java Fri Feb 27 08:53:05 2009 @@ -19,12 +19,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; - import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; -import javax.xml.bind.Marshaller; -import javax.xml.bind.Unmarshaller; import org.apache.camel.Exchange; import org.apache.camel.spi.DataFormat; @@ -41,8 +38,6 @@ private String contextPath; private boolean prettyPrint = true; private boolean ignoreJAXBElement = true; - private Marshaller marshaller; - private Unmarshaller unmarshaller; public JaxbDataFormat() { } @@ -57,7 +52,8 @@ public void marshal(Exchange exchange, Object graph, OutputStream stream) throws IOException { try { - getMarshaller().marshal(graph, stream); + // must create a new instance of marshaller as its not thred safe + getContext().createMarshaller().marshal(graph, stream); } catch (JAXBException e) { throw IOHelper.createIOException(e); } @@ -65,7 +61,8 @@ public Object unmarshal(Exchange exchange, InputStream stream) throws IOException, ClassNotFoundException { try { - Object answer = getUnmarshaller().unmarshal(stream); + // must create a new instance of unmarshaller as its not thred safe + Object answer = getContext().createUnmarshaller().unmarshal(stream); if (answer instanceof JAXBElement && isIgnoreJAXBElement()) { answer = ((JAXBElement)answer).getValue(); } @@ -85,7 +82,7 @@ ignoreJAXBElement = flag; } - public JAXBContext getContext() throws JAXBException { + public synchronized JAXBContext getContext() throws JAXBException { if (context == null) { context = createContext(); } @@ -104,17 +101,6 @@ this.contextPath = contextPath; } - public Marshaller getMarshaller() throws JAXBException { - if (marshaller == null) { - marshaller = getContext().createMarshaller(); - } - return marshaller; - } - - public void setMarshaller(Marshaller marshaller) { - this.marshaller = marshaller; - } - public boolean isPrettyPrint() { return prettyPrint; } @@ -123,17 +109,6 @@ this.prettyPrint = prettyPrint; } - public Unmarshaller getUnmarshaller() throws JAXBException { - if (unmarshaller == null) { - unmarshaller = getContext().createUnmarshaller(); - } - return unmarshaller; - } - - public void setUnmarshaller(Unmarshaller unmarshaller) { - this.unmarshaller = unmarshaller; - } - protected JAXBContext createContext() throws JAXBException { if (contextPath != null) { return JAXBContext.newInstance(contextPath); Added: camel/trunk/components/camel-jaxb/src/test/java/org/apache/camel/example/DataFormatConcurrentTest.java URL: http://svn.apache.org/viewvc/camel/trunk/components/camel-jaxb/src/test/java/org/apache/camel/example/DataFormatConcurrentTest.java?rev=748436&view=auto ============================================================================== --- camel/trunk/components/camel-jaxb/src/test/java/org/apache/camel/example/DataFormatConcurrentTest.java (added) +++ camel/trunk/components/camel-jaxb/src/test/java/org/apache/camel/example/DataFormatConcurrentTest.java Fri Feb 27 08:53:05 2009 @@ -0,0 +1,78 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.example; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.apache.camel.ContextTestSupport; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.converter.jaxb.JaxbDataFormat; +import org.apache.camel.spi.DataFormat; + +/** + * @version $Revision$ + */ +public class DataFormatConcurrentTest extends ContextTestSupport { + + private int size = 2000; + + public void testSendConcurrent() throws Exception { + MockEndpoint mock = getMockEndpoint("mock:result"); + mock.expectedMessageCount(size); + + // wait for seda consumer to start up properly + Thread.sleep(1000); + + ExecutorService executor = Executors.newCachedThreadPool(); + for (int i = 0; i < size; i++) { + + // sleep a little so we interleave with the marshaller + try { + Thread.sleep(1, 500); + } catch (InterruptedException e) { + // ignore + } + + executor.execute(new Runnable() { + public void run() { + PurchaseOrder bean = new PurchaseOrder(); + bean.setName("Beer"); + bean.setAmount(23); + bean.setPrice(2.5); + + template.sendBody("seda:start?size=" + size + "&concurrentConsumers=5", bean); + } + }); + } + + assertMockEndpointsSatisfied(); + } + + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + public void configure() { + DataFormat jaxb = new JaxbDataFormat("org.apache.camel.example"); + + // use seda that supports concurrent consumers for concurrency + from("seda:start?size=" + size + "&concurrentConsumers=5").marshal(jaxb).convertBodyTo(String.class).to("mock:result"); + } + }; + } + +} \ No newline at end of file Propchange: camel/trunk/components/camel-jaxb/src/test/java/org/apache/camel/example/DataFormatConcurrentTest.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: camel/trunk/components/camel-jaxb/src/test/java/org/apache/camel/example/DataFormatConcurrentTest.java ------------------------------------------------------------------------------ svn:keywords = Rev Date Copied: camel/trunk/components/camel-jaxb/src/test/java/org/apache/camel/example/DataFormatDataSetTest.java (from r748394, camel/trunk/components/camel-jaxb/src/test/java/org/apache/camel/example/DataFormatTest.java) URL: http://svn.apache.org/viewvc/camel/trunk/components/camel-jaxb/src/test/java/org/apache/camel/example/DataFormatDataSetTest.java?p2=camel/trunk/components/camel-jaxb/src/test/java/org/apache/camel/example/DataFormatDataSetTest.java&p1=camel/trunk/components/camel-jaxb/src/test/java/org/apache/camel/example/DataFormatTest.java&r1=748394&r2=748436&rev=748436&view=diff ============================================================================== --- camel/trunk/components/camel-jaxb/src/test/java/org/apache/camel/example/DataFormatTest.java (original) +++ camel/trunk/components/camel-jaxb/src/test/java/org/apache/camel/example/DataFormatDataSetTest.java Fri Feb 27 08:53:05 2009 @@ -16,45 +16,46 @@ */ package org.apache.camel.example; +import javax.naming.Context; + import org.apache.camel.ContextTestSupport; import org.apache.camel.builder.RouteBuilder; -import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.component.dataset.SimpleDataSet; import org.apache.camel.converter.jaxb.JaxbDataFormat; import org.apache.camel.spi.DataFormat; /** * @version $Revision$ */ -public class DataFormatTest extends ContextTestSupport { +public class DataFormatDataSetTest extends ContextTestSupport { + + public void testConcurrentMarshall() throws Exception { + assertMockEndpointsSatisfied(); + } - public void testMarshalThenUnmarshalBean() throws Exception { + @Override + protected Context createJndiContext() throws Exception { PurchaseOrder bean = new PurchaseOrder(); bean.setName("Beer"); bean.setAmount(23); bean.setPrice(2.5); - MockEndpoint resultEndpoint = resolveMandatoryEndpoint("mock:result", MockEndpoint.class); - resultEndpoint.expectedBodiesReceived(bean); - - template.sendBody("direct:start", bean); - - resultEndpoint.assertIsSatisfied(); + SimpleDataSet ds = new SimpleDataSet(); + ds.setDefaultBody(bean); + ds.setSize(200); + + Context context = super.createJndiContext(); + context.bind("beer", ds); + return context; } - protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { public void configure() { - DataFormat jaxb = new JaxbDataFormat("org.apache.camel.example"); - from("direct:start"). - marshal(jaxb). - to("direct:marshalled"); - - from("direct:marshalled"). - unmarshal(jaxb). - to("mock:result"); + // use 5 concurrent threads to do marshalling + from("dataset:beer").thread(5).marshal(jaxb).to("dataset:beer"); } }; } Propchange: camel/trunk/components/camel-jaxb/src/test/java/org/apache/camel/example/DataFormatDataSetTest.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: camel/trunk/components/camel-jaxb/src/test/java/org/apache/camel/example/DataFormatDataSetTest.java ------------------------------------------------------------------------------ svn:keywords = Rev Date Modified: camel/trunk/components/camel-jaxb/src/test/java/org/apache/camel/example/JAXBConvertTest.java URL: http://svn.apache.org/viewvc/camel/trunk/components/camel-jaxb/src/test/java/org/apache/camel/example/JAXBConvertTest.java?rev=748436&r1=748435&r2=748436&view=diff ============================================================================== --- camel/trunk/components/camel-jaxb/src/test/java/org/apache/camel/example/JAXBConvertTest.java (original) +++ camel/trunk/components/camel-jaxb/src/test/java/org/apache/camel/example/JAXBConvertTest.java Fri Feb 27 08:53:05 2009 @@ -43,6 +43,25 @@ assertNotNull("Purchase order should not be null!", purchaseOrder); assertEquals("name", "foo", purchaseOrder.getName()); assertEquals("amount", 123.45, purchaseOrder.getAmount()); + assertEquals("price", 2.22, purchaseOrder.getPrice()); + } + + public void testConverterTwice() throws Exception { + PurchaseOrder purchaseOrder = converter.convertTo(PurchaseOrder.class, + "<purchaseOrder name='foo' amount='123.45' price='2.22'/>"); + + assertNotNull("Purchase order should not be null!", purchaseOrder); + assertEquals("name", "foo", purchaseOrder.getName()); + assertEquals("amount", 123.45, purchaseOrder.getAmount()); + assertEquals("price", 2.22, purchaseOrder.getPrice()); + + PurchaseOrder purchaseOrder2 = converter.convertTo(PurchaseOrder.class, + "<purchaseOrder name='bar' amount='5.12' price='3.33'/>"); + + assertNotNull("Purchase order should not be null!", purchaseOrder2); + assertEquals("name", "bar", purchaseOrder2.getName()); + assertEquals("amount", 5.12, purchaseOrder2.getAmount()); + assertEquals("amount", 3.33, purchaseOrder2.getPrice()); } public void testStreamShouldBeClosed() throws Exception {