You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by cm...@apache.org on 2013/08/20 23:28:02 UTC

git commit: CAMEL-6630: Validation using JAXB format is not thread safe

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 <cm...@apache.org>
Authored: Tue Aug 20 23:27:46 2013 +0200
Committer: cmueller <cm...@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