Updated Branches: refs/heads/master 467a1eb20 -> a87a8d129
CAMEL-6630: Validation using JAXB format is not thread safe Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/a87a8d12 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/a87a8d12 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/a87a8d12 Branch: refs/heads/master Commit: a87a8d12980ca367f0c667dc8a8c709e6c99673c Parents: 467a1eb Author: cmueller <cmuel...@apache.org> Authored: Tue Aug 20 23:27:46 2013 +0200 Committer: cmueller <cmuel...@apache.org> Committed: Tue Aug 20 23:27:55 2013 +0200 ---------------------------------------------------------------------- .../camel/converter/jaxb/JaxbDataFormat.java | 82 ++++++++++--- ...rrentJaxbDataFormatSchemaValidationTest.java | 122 +++++++++++++++++++ 2 files changed, 185 insertions(+), 19 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/a87a8d12/components/camel-jaxb/src/main/java/org/apache/camel/converter/jaxb/JaxbDataFormat.java ---------------------------------------------------------------------- diff --git a/components/camel-jaxb/src/main/java/org/apache/camel/converter/jaxb/JaxbDataFormat.java b/components/camel-jaxb/src/main/java/org/apache/camel/converter/jaxb/JaxbDataFormat.java index c8a8bb1..a757b90 100644 --- a/components/camel-jaxb/src/main/java/org/apache/camel/converter/jaxb/JaxbDataFormat.java +++ b/components/camel-jaxb/src/main/java/org/apache/camel/converter/jaxb/JaxbDataFormat.java @@ -25,6 +25,8 @@ import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; import javax.xml.XMLConstants; import javax.xml.bind.JAXBContext; @@ -44,8 +46,6 @@ import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; -import org.xml.sax.SAXException; - import org.apache.camel.CamelContext; import org.apache.camel.CamelContextAware; import org.apache.camel.Exchange; @@ -58,6 +58,7 @@ import org.apache.camel.util.ObjectHelper; import org.apache.camel.util.ResourceHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.xml.sax.SAXException; /** @@ -69,7 +70,9 @@ import org.slf4j.LoggerFactory; public class JaxbDataFormat extends ServiceSupport implements DataFormat, CamelContextAware { private static final Logger LOG = LoggerFactory.getLogger(JaxbDataFormat.class); - private static final SchemaFactory SCHEMA_FACTORY = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + private static final BlockingQueue<SchemaFactory> SCHEMA_FACTORY_POOL = new LinkedBlockingQueue<SchemaFactory>(); + + private SchemaFactory schemaFactory; private CamelContext camelContext; private JAXBContext context; private String contextPath; @@ -215,6 +218,17 @@ public class JaxbDataFormat extends ServiceSupport implements DataFormat, CamelC this.contextPath = contextPath; } + public SchemaFactory getSchemaFactory() { + if (schemaFactory == null) { + return getOrCreateSchemaFactory(); + } + return schemaFactory; + } + + public void setSchema(SchemaFactory schemaFactory) { + this.schemaFactory = schemaFactory; + } + public String getSchema() { return schema; } @@ -344,30 +358,42 @@ public class JaxbDataFormat extends ServiceSupport implements DataFormat, CamelC protected Unmarshaller createUnmarshaller() throws JAXBException, SAXException, FileNotFoundException, MalformedURLException { Unmarshaller unmarshaller = getContext().createUnmarshaller(); if (schema != null) { - Schema newSchema = SCHEMA_FACTORY.newSchema(getSources()); - unmarshaller.setSchema(newSchema); - unmarshaller.setEventHandler(new ValidationEventHandler() { - public boolean handleEvent(ValidationEvent event) { - // stop unmarshalling if the event is an ERROR or FATAL ERROR - return event.getSeverity() == ValidationEvent.WARNING; - } - }); + SchemaFactory factory = getOrCreateSchemaFactory(); + try { + Schema newSchema = factory.newSchema(getSources()); + unmarshaller.setSchema(newSchema); + unmarshaller.setEventHandler(new ValidationEventHandler() { + public boolean handleEvent(ValidationEvent event) { + // stop unmarshalling if the event is an ERROR or FATAL ERROR + return event.getSeverity() == ValidationEvent.WARNING; + } + }); + } finally { + returnSchemaFactory(factory); + } } + return unmarshaller; } protected Marshaller createMarshaller() throws JAXBException, SAXException, FileNotFoundException, MalformedURLException { Marshaller marshaller = getContext().createMarshaller(); if (schema != null) { - Schema newSchema = SCHEMA_FACTORY.newSchema(getSources()); - marshaller.setSchema(newSchema); - marshaller.setEventHandler(new ValidationEventHandler() { - public boolean handleEvent(ValidationEvent event) { - // stop marshalling if the event is an ERROR or FATAL ERROR - return event.getSeverity() == ValidationEvent.WARNING; - } - }); + SchemaFactory factory = getOrCreateSchemaFactory(); + try { + Schema newSchema = factory.newSchema(getSources()); + marshaller.setSchema(newSchema); + marshaller.setEventHandler(new ValidationEventHandler() { + public boolean handleEvent(ValidationEvent event) { + // stop marshalling if the event is an ERROR or FATAL ERROR + return event.getSeverity() == ValidationEvent.WARNING; + } + }); + } finally { + returnSchemaFactory(factory); + } } + return marshaller; } @@ -381,4 +407,22 @@ public class JaxbDataFormat extends ServiceSupport implements DataFormat, CamelC } return sources; } + + private SchemaFactory getOrCreateSchemaFactory() { + SchemaFactory factory = SCHEMA_FACTORY_POOL.poll(); + if (factory == null) { + factory = createSchemaFactory(); + } + return factory; + } + + public static SchemaFactory createSchemaFactory() { + return SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + } + + private void returnSchemaFactory(SchemaFactory factory) { + if (factory != schemaFactory) { + SCHEMA_FACTORY_POOL.offer(factory); + } + } } http://git-wip-us.apache.org/repos/asf/camel/blob/a87a8d12/components/camel-jaxb/src/test/java/org/apache/camel/converter/jaxb/ConcurrentJaxbDataFormatSchemaValidationTest.java ---------------------------------------------------------------------- diff --git a/components/camel-jaxb/src/test/java/org/apache/camel/converter/jaxb/ConcurrentJaxbDataFormatSchemaValidationTest.java b/components/camel-jaxb/src/test/java/org/apache/camel/converter/jaxb/ConcurrentJaxbDataFormatSchemaValidationTest.java new file mode 100644 index 0000000..15ea750 --- /dev/null +++ b/components/camel-jaxb/src/test/java/org/apache/camel/converter/jaxb/ConcurrentJaxbDataFormatSchemaValidationTest.java @@ -0,0 +1,122 @@ +/** + * 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.converter.jaxb; + +import java.util.concurrent.TimeUnit; + +import org.apache.camel.EndpointInject; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.converter.jaxb.address.Address; +import org.apache.camel.converter.jaxb.person.Person; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Test; + +public class ConcurrentJaxbDataFormatSchemaValidationTest extends CamelTestSupport { + + @EndpointInject(uri = "mock:marshall") + private MockEndpoint mockMarshall; + + @EndpointInject(uri = "mock:unmarshall") + private MockEndpoint mockUnmarshall; + + private int testCount = 1000; + private int concurrencyLevel = 10; + + @Test + public void concurrentMarshallSuccess() throws Exception { + mockMarshall.expectedMessageCount(testCount); + + Address address = new Address(); + address.setAddressLine1("Hauptstr. 1; 01129 Entenhausen"); + Person person = new Person(); + person.setFirstName("Christian"); + person.setLastName("Mueller"); + person.setAge(Integer.valueOf(36)); + person.setAddress(address); + + long start = System.currentTimeMillis(); + for (int i = 0; i < testCount; i++) { + template.sendBody("seda:marshall", person); + } + + assertMockEndpointsSatisfied(); + log.info("Validation of {} messages took {} ms",testCount, (System.currentTimeMillis() - start)); + + String payload = mockMarshall.getExchanges().get(0).getIn().getBody(String.class); + log.info(payload); + + assertTrue(payload.startsWith("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>")); + assertTrue(payload.contains("<person xmlns=\"person.jaxb.converter.camel.apache.org\" xmlns:ns2=\"address.jaxb.converter.camel.apache.org\">")); + assertTrue(payload.contains("<firstName>Christian</firstName>")); + assertTrue(payload.contains("<lastName>Mueller</lastName>")); + assertTrue(payload.contains("<age>36</age>")); + assertTrue(payload.contains("<address>")); + assertTrue(payload.contains("<ns2:addressLine1>Hauptstr. 1; 01129 Entenhausen</ns2:addressLine1>")); + assertTrue(payload.contains("</address>")); + assertTrue(payload.contains("</person>")); + } + + @Test + public void concurrentUnmarshall() throws Exception { + mockUnmarshall.expectedMessageCount(testCount); + + String xml = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>") + .append("<person xmlns=\"person.jaxb.converter.camel.apache.org\" xmlns:ns2=\"address.jaxb.converter.camel.apache.org\">") + .append("<firstName>Christian</firstName>") + .append("<lastName>Mueller</lastName>") + .append("<age>36</age>") + .append("<address>") + .append("<ns2:addressLine1>Hauptstr. 1; 01129 Entenhausen</ns2:addressLine1>") + .append("</address>") + .append("</person>") + .toString(); + + long start = System.currentTimeMillis(); + for (int i = 0; i < testCount; i++) { + template.sendBody("seda:unmarshall", xml); + } + + assertMockEndpointsSatisfied(20, TimeUnit.SECONDS); + log.info("Validation of {} messages took {} ms",testCount, (System.currentTimeMillis() - start)); + + Person person = mockUnmarshall.getExchanges().get(0).getIn().getBody(Person.class); + + assertEquals("Christian", person.getFirstName()); + assertEquals("Mueller", person.getLastName()); + assertEquals(Integer.valueOf(36), person.getAge()); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + public void configure() throws Exception { + JaxbDataFormat jaxbDataFormat = new JaxbDataFormat(); + jaxbDataFormat.setContextPath(Person.class.getPackage().getName()); + jaxbDataFormat.setSchema("classpath:person.xsd,classpath:address.xsd"); + + from("seda:marshall?concurrentConsumers=" + concurrencyLevel) + .marshal(jaxbDataFormat) + .to("mock:marshall"); + + from("seda:unmarshall?concurrentConsumers=" + concurrencyLevel) + .unmarshal(jaxbDataFormat) + .to("mock:unmarshall"); + } + }; + } +} \ No newline at end of file