You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@cxf.apache.org by Daniel Kulp <dk...@apache.org> on 2017/03/07 13:09:56 UTC
Re: cxf git commit: [CXF-7269]schemavalidate failed when use mtom and
Provider
Freeman,
I believe this commit would introduce CXF-7185 for the source data binding…. Can you adjust accordingly? You can see what was done for JAXB.
Thanks!
Dan
> On Mar 7, 2017, at 3:44 AM, ffang@apache.org wrote:
>
> Repository: cxf
> Updated Branches:
> refs/heads/master 09fb22b25 -> 08c9194ce
>
>
> [CXF-7269]schemavalidate failed when use mtom and Provider
>
>
> Project: http://git-wip-us.apache.org/repos/asf/cxf/repo
> Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/08c9194c
> Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/08c9194c
> Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/08c9194c
>
> Branch: refs/heads/master
> Commit: 08c9194ce2c567ebfe59f59ee628b1197d90fd43
> Parents: 09fb22b
> Author: Freeman Fang <fr...@gmail.com>
> Authored: Tue Mar 7 16:44:03 2017 +0800
> Committer: Freeman Fang <fr...@gmail.com>
> Committed: Tue Mar 7 16:44:03 2017 +0800
>
> ----------------------------------------------------------------------
> .../databinding/source/XMLStreamDataWriter.java | 111 ++++++++++++++++-
> .../cxf/systest/mtom/ClientMtomXopTest.java | 119 ++++++++++++++++++-
> testutils/pom.xml | 4 +
> .../cxf/mtom_xop/TestMtomProviderImpl.java | 104 ++++++++++++++++
> testutils/src/main/resources/wsdl/mtom_xop.wsdl | 3 +
> 5 files changed, 335 insertions(+), 6 deletions(-)
> ----------------------------------------------------------------------
>
>
> http://git-wip-us.apache.org/repos/asf/cxf/blob/08c9194c/core/src/main/java/org/apache/cxf/databinding/source/XMLStreamDataWriter.java
> ----------------------------------------------------------------------
> diff --git a/core/src/main/java/org/apache/cxf/databinding/source/XMLStreamDataWriter.java b/core/src/main/java/org/apache/cxf/databinding/source/XMLStreamDataWriter.java
> index 8bd9b71..afbcca0 100644
> --- a/core/src/main/java/org/apache/cxf/databinding/source/XMLStreamDataWriter.java
> +++ b/core/src/main/java/org/apache/cxf/databinding/source/XMLStreamDataWriter.java
> @@ -29,12 +29,17 @@ import javax.xml.stream.XMLStreamWriter;
> import javax.xml.transform.Source;
> import javax.xml.transform.dom.DOMSource;
> import javax.xml.validation.Schema;
> +import javax.xml.validation.Validator;
>
> import org.w3c.dom.Document;
> import org.w3c.dom.DocumentFragment;
> import org.w3c.dom.Node;
> +import org.w3c.dom.NodeList;
>
> +import org.xml.sax.ErrorHandler;
> import org.xml.sax.SAXException;
> +import org.xml.sax.SAXParseException;
> +
>
> import org.apache.cxf.common.i18n.Message;
> import org.apache.cxf.common.logging.LogUtils;
> @@ -46,10 +51,16 @@ import org.apache.cxf.staxutils.StaxUtils;
> import org.apache.cxf.staxutils.W3CDOMStreamWriter;
>
> public class XMLStreamDataWriter implements DataWriter<XMLStreamWriter> {
> +
> private static final Logger LOG = LogUtils.getL7dLogger(XMLStreamDataWriter.class);
>
> +
> private Schema schema;
>
> + public XMLStreamDataWriter() {
> +
> + }
> +
> public void write(Object obj, MessagePartInfo part, XMLStreamWriter output) {
> write(obj, output);
> }
> @@ -61,7 +72,10 @@ public class XMLStreamDataWriter implements DataWriter<XMLStreamWriter> {
> DataSource ds = (DataSource)obj;
> if (schema != null) {
> DOMSource domSource = new DOMSource(StaxUtils.read(ds.getInputStream()));
> - schema.newValidator().validate(domSource);
> + Validator schemaValidator = schema.newValidator();
> + schemaValidator.setErrorHandler(
> + new MtomValidationErrorHandler(schemaValidator.getErrorHandler(), domSource.getNode()));
> + schemaValidator.validate(domSource);
> StaxUtils.copy(domSource, writer);
> } else {
> reader = StaxUtils.createXMLStreamReader(ds.getInputStream());
> @@ -71,7 +85,10 @@ public class XMLStreamDataWriter implements DataWriter<XMLStreamWriter> {
>
> } else if (obj instanceof Node) {
> if (schema != null) {
> - schema.newValidator().validate(new DOMSource((Node)obj));
> + Validator schemaValidator = schema.newValidator();
> + schemaValidator.setErrorHandler(
> + new MtomValidationErrorHandler(schemaValidator.getErrorHandler(), (Node)obj));
> + schemaValidator.validate(new DOMSource((Node)obj));
> }
> Node nd = (Node)obj;
> writeNode(nd, writer);
> @@ -82,7 +99,10 @@ public class XMLStreamDataWriter implements DataWriter<XMLStreamWriter> {
> //make the source re-readable.
> s = new DOMSource(StaxUtils.read(s));
> }
> - schema.newValidator().validate(s);
> + Validator schemaValidator = schema.newValidator();
> + schemaValidator.setErrorHandler(
> + new MtomValidationErrorHandler(schemaValidator.getErrorHandler(), ((DOMSource)s).getNode()));
> + schemaValidator.validate(s);
> }
> if (s instanceof DOMSource
> && ((DOMSource) s).getNode() == null) {
> @@ -152,5 +172,90 @@ public class XMLStreamDataWriter implements DataWriter<XMLStreamWriter> {
>
> public void setProperty(String key, Object value) {
> }
> +
> + private static class MtomValidationErrorHandler implements ErrorHandler {
> + private ErrorHandler origErrorHandler;
> + private Node node;
> +
> + MtomValidationErrorHandler(ErrorHandler origErrorHandler, Node node) {
> + this.origErrorHandler = origErrorHandler;
> + this.node = node;
> + }
> +
> +
> + @Override
> + public void warning(SAXParseException exception) throws SAXException {
> +
> + if (this.origErrorHandler != null) {
> + this.origErrorHandler.warning(exception);
> + } else {
> + // do nothing
> + }
> + }
> +
> + @Override
> + public void error(SAXParseException exception) throws SAXException {
> + if (this.isCVC312Exception(exception)) {
> + String elementName = this.getAttachmentElementName(exception);
> + if (node != null && this.findIncludeNode(node, elementName)) {
> + return;
> + }
> + }
> +
> + if (this.origErrorHandler != null) {
> + this.origErrorHandler.error(exception);
> + } else {
> + throw exception;
> + }
> +
> + }
> +
> + @Override
> + public void fatalError(SAXParseException exception) throws SAXException {
> + if (this.origErrorHandler != null) {
> + this.origErrorHandler.fatalError(exception);
> + } else {
> + throw exception;
> + }
> +
> + }
> +
> + private boolean isCVC312Exception(SAXParseException exception) {
> + String msg = exception.getMessage();
> + return msg.startsWith("cvc-type.3.1.2: ")
> + && msg.endsWith("is a simple type, so it must have no element information item [children].");
> +
> +
> + }
> +
> + private String getAttachmentElementName(SAXParseException exception) {
> + String msg = exception.getMessage();
> + String str[] = msg.split("'");
> + return str[1];
> + }
> +
> + private boolean findIncludeNode(Node checkNode, String mtomElement) {
> + boolean ret = false;
> + NodeList nList = checkNode.getChildNodes();
> + for (int i = 0; i < nList.getLength(); i++) {
> + Node nNode = nList.item(i);
> + if (nNode.getLocalName() != null
> + && nNode.getLocalName().equals(mtomElement)) {
> + NodeList subNodeList = nNode.getChildNodes();
> + for (int j = 0; j < subNodeList.getLength(); j++) {
> + Node subNode = subNodeList.item(j);
> + if (subNode.getNamespaceURI().equals("http://www.w3.org/2004/08/xop/include")
> + && subNode.getLocalName().equals("Include")) {
> + // This is the Mtom element which break the SchemaValidation so ignore this
> + return true;
> + }
> + }
> + } else {
> + ret = findIncludeNode(nNode, mtomElement);
> + }
> + }
> + return ret;
> + }
> + }
>
> }
>
> http://git-wip-us.apache.org/repos/asf/cxf/blob/08c9194c/systests/uncategorized/src/test/java/org/apache/cxf/systest/mtom/ClientMtomXopTest.java
> ----------------------------------------------------------------------
> diff --git a/systests/uncategorized/src/test/java/org/apache/cxf/systest/mtom/ClientMtomXopTest.java b/systests/uncategorized/src/test/java/org/apache/cxf/systest/mtom/ClientMtomXopTest.java
> index 184bd64..1616b48 100644
> --- a/systests/uncategorized/src/test/java/org/apache/cxf/systest/mtom/ClientMtomXopTest.java
> +++ b/systests/uncategorized/src/test/java/org/apache/cxf/systest/mtom/ClientMtomXopTest.java
> @@ -38,10 +38,10 @@ import org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor;
> import org.apache.cxf.endpoint.Client;
> import org.apache.cxf.endpoint.ClientImpl;
> import org.apache.cxf.endpoint.Endpoint;
> -import org.apache.cxf.ext.logging.LoggingInInterceptor;
> -import org.apache.cxf.ext.logging.LoggingOutInterceptor;
> import org.apache.cxf.frontend.ClientProxy;
> import org.apache.cxf.helpers.IOUtils;
> +import org.apache.cxf.interceptor.LoggingInInterceptor;
> +import org.apache.cxf.interceptor.LoggingOutInterceptor;
> import org.apache.cxf.jaxws.EndpointImpl;
> import org.apache.cxf.jaxws.JaxWsClientProxy;
> import org.apache.cxf.jaxws.binding.soap.SOAPBindingImpl;
> @@ -51,6 +51,7 @@ import org.apache.cxf.message.Message;
> import org.apache.cxf.mime.TestMtom;
> import org.apache.cxf.mime.types.XopStringType;
> import org.apache.cxf.mtom_xop.TestMtomImpl;
> +import org.apache.cxf.mtom_xop.TestMtomProviderImpl;
> import org.apache.cxf.service.Service;
> import org.apache.cxf.service.model.EndpointInfo;
> import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
> @@ -63,7 +64,9 @@ import org.junit.Test;
> public class ClientMtomXopTest extends AbstractBusClientServerTestBase {
> public static final String PORT = allocatePort(ClientMtomXopTest.class);
> public static final QName MTOM_PORT = new QName("http://cxf.apache.org/mime", "TestMtomPort");
> + public static final QName MTOM_PORT_PROVIDER = new QName("http://cxf.apache.org/mime", "TestMtomProviderPort");
> public static final QName MTOM_SERVICE = new QName("http://cxf.apache.org/mime", "TestMtomService");
> +
>
>
> public static class Server extends AbstractBusTestServerBase {
> @@ -71,14 +74,21 @@ public class ClientMtomXopTest extends AbstractBusClientServerTestBase {
> protected void run() {
> Object implementor = new TestMtomImpl();
> String address = "http://localhost:" + PORT + "/mime-test";
> + String addressProvider = "http://localhost:" + PORT + "/mime-test-provider";
> try {
> jaxep = (EndpointImpl) javax.xml.ws.Endpoint.publish(address, implementor);
> Endpoint ep = jaxep.getServer().getEndpoint();
> ep.getInInterceptors().add(new TestMultipartMessageInterceptor());
> ep.getOutInterceptors().add(new TestAttachmentOutInterceptor());
> -
> + jaxep.getInInterceptors().add(new LoggingInInterceptor());
> + jaxep.getOutInterceptors().add(new LoggingOutInterceptor());
> SOAPBinding jaxWsSoapBinding = (SOAPBinding) jaxep.getBinding();
> jaxWsSoapBinding.setMTOMEnabled(true);
> + EndpointImpl endpoint =
> + (EndpointImpl)javax.xml.ws.Endpoint.publish(addressProvider, new TestMtomProviderImpl());
> + endpoint.getProperties().put("schema-validation-enabled", "true");
> + endpoint.getInInterceptors().add(new LoggingInInterceptor());
> + endpoint.getOutInterceptors().add(new LoggingOutInterceptor());
>
> } catch (Exception e) {
> Thread.currentThread().interrupt();
> @@ -197,6 +207,109 @@ public class ClientMtomXopTest extends AbstractBusClientServerTestBase {
> throw ex;
> }
> }
> +
> +
> + @Test
> + public void testMtomXopProvider() throws Exception {
> + TestMtom mtomPort = createPort(MTOM_SERVICE, MTOM_PORT_PROVIDER, TestMtom.class, true, true);
> + try {
> + Holder<DataHandler> param = new Holder<DataHandler>();
> + Holder<String> name;
> + byte bytes[];
> + InputStream in;
> +
> + InputStream pre = this.getClass().getResourceAsStream("/wsdl/mtom_xop.wsdl");
> + int fileSize = 0;
> + for (int i = pre.read(); i != -1; i = pre.read()) {
> + fileSize++;
> + }
> +
> + int count = 50;
> + byte[] data = new byte[fileSize * count];
> + for (int x = 0; x < count; x++) {
> + this.getClass().getResourceAsStream("/wsdl/mtom_xop.wsdl").read(data,
> + fileSize * x,
> + fileSize);
> + }
> +
> + Object[] validationTypes = new Object[]{Boolean.TRUE, SchemaValidationType.IN, SchemaValidationType.BOTH};
> +
> + for (Object validationType : validationTypes) {
> + ((BindingProvider)mtomPort).getRequestContext().put(Message.SCHEMA_VALIDATION_ENABLED,
> + validationType);
> +
> + param.value = new DataHandler(new ByteArrayDataSource(data, "application/octet-stream"));
> + name = new Holder<String>("call detail");
> + mtomPort.testXop(name, param);
> + assertEquals("name unchanged", "return detail + call detail", name.value);
> + assertNotNull(param.value);
> +
> + in = param.value.getInputStream();
> + bytes = IOUtils.readBytesFromStream(in);
> + assertEquals(data.length, bytes.length);
> + in.close();
> +
> + param.value = new DataHandler(new ByteArrayDataSource(data, "application/octet-stream"));
> + name = new Holder<String>("call detail");
> + mtomPort.testXop(name, param);
> + assertEquals("name unchanged", "return detail + call detail", name.value);
> + assertNotNull(param.value);
> +
> + in = param.value.getInputStream();
> + bytes = IOUtils.readBytesFromStream(in);
> + assertEquals(data.length, bytes.length);
> + in.close();
> + }
> +
> + validationTypes = new Object[]{Boolean.FALSE, SchemaValidationType.OUT, SchemaValidationType.NONE};
> + for (Object validationType : validationTypes) {
> + ((BindingProvider)mtomPort).getRequestContext().put(Message.SCHEMA_VALIDATION_ENABLED,
> + validationType);
> + SAAJOutInterceptor saajOut = new SAAJOutInterceptor();
> + SAAJInInterceptor saajIn = new SAAJInInterceptor();
> +
> + param.value = new DataHandler(new ByteArrayDataSource(data, "application/octet-stream"));
> + name = new Holder<String>("call detail");
> + mtomPort.testXop(name, param);
> + assertEquals("name unchanged", "return detail + call detail", name.value);
> + assertNotNull(param.value);
> +
> + in = param.value.getInputStream();
> + bytes = IOUtils.readBytesFromStream(in);
> + assertEquals(data.length, bytes.length);
> + in.close();
> +
> + ClientProxy.getClient(mtomPort).getInInterceptors().add(saajIn);
> + ClientProxy.getClient(mtomPort).getInInterceptors().add(saajOut);
> + param.value = new DataHandler(new ByteArrayDataSource(data, "application/octet-stream"));
> + name = new Holder<String>("call detail");
> + mtomPort.testXop(name, param);
> + assertEquals("name unchanged", "return detail + call detail", name.value);
> + assertNotNull(param.value);
> +
> + in = param.value.getInputStream();
> + bytes = IOUtils.readBytesFromStream(in);
> + assertEquals(data.length, bytes.length);
> + in.close();
> +
> + ClientProxy.getClient(mtomPort).getInInterceptors().remove(saajIn);
> + ClientProxy.getClient(mtomPort).getInInterceptors().remove(saajOut);
> + }
> + } catch (UndeclaredThrowableException ex) {
> + throw (Exception)ex.getCause();
> + } catch (Exception ex) {
> + if (ex.getMessage().contains("Connection reset")
> + && System.getProperty("java.specification.version", "1.5").contains("1.6")) {
> + //There seems to be a bug/interaction with Java 1.6 and Jetty where
> + //Jetty will occasionally send back a RST prior to all the data being
> + //sent back to the client when using localhost (which is what we do)
> + //we'll ignore for now
> + return;
> + }
> + System.out.println(System.getProperties());
> + throw ex;
> + }
> + }
>
> @Test
> public void testMtomWithFileName() throws Exception {
>
> http://git-wip-us.apache.org/repos/asf/cxf/blob/08c9194c/testutils/pom.xml
> ----------------------------------------------------------------------
> diff --git a/testutils/pom.xml b/testutils/pom.xml
> index bd481ad..fb611ab 100644
> --- a/testutils/pom.xml
> +++ b/testutils/pom.xml
> @@ -53,6 +53,10 @@
> <optional>true</optional>
> </dependency>
> <dependency>
> + <groupId>org.apache.geronimo.specs</groupId>
> + <artifactId>geronimo-javamail_1.4_spec</artifactId>
> + </dependency>
> + <dependency>
> <groupId>org.springframework</groupId>
> <artifactId>spring-core</artifactId>
> <scope>provided</scope>
>
> http://git-wip-us.apache.org/repos/asf/cxf/blob/08c9194c/testutils/src/main/java/org/apache/cxf/mtom_xop/TestMtomProviderImpl.java
> ----------------------------------------------------------------------
> diff --git a/testutils/src/main/java/org/apache/cxf/mtom_xop/TestMtomProviderImpl.java b/testutils/src/main/java/org/apache/cxf/mtom_xop/TestMtomProviderImpl.java
> new file mode 100644
> index 0000000..de1f442
> --- /dev/null
> +++ b/testutils/src/main/java/org/apache/cxf/mtom_xop/TestMtomProviderImpl.java
> @@ -0,0 +1,104 @@
> +/**
> + * 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.cxf.mtom_xop;
> +
> +import java.io.InputStream;
> +
> +import javax.activation.DataHandler;
> +import javax.mail.util.ByteArrayDataSource;
> +import javax.xml.soap.AttachmentPart;
> +import javax.xml.soap.MessageFactory;
> +import javax.xml.soap.SOAPBody;
> +import javax.xml.soap.SOAPBodyElement;
> +import javax.xml.soap.SOAPElement;
> +import javax.xml.soap.SOAPEnvelope;
> +import javax.xml.soap.SOAPMessage;
> +import javax.xml.soap.SOAPPart;
> +import javax.xml.ws.Provider;
> +import javax.xml.ws.Service.Mode;
> +import javax.xml.ws.ServiceMode;
> +import javax.xml.ws.WebServiceProvider;
> +import javax.xml.ws.soap.MTOM;
> +
> +
> +@WebServiceProvider(portName = "TestMtomProviderPort",
> +serviceName = "TestMtomService",
> +targetNamespace = "http://cxf.apache.org/mime",
> +wsdlLocation = "testutils/mtom_xop.wsdl")
> +@ServiceMode(value = Mode.MESSAGE)
> +@MTOM
> +
> +public class TestMtomProviderImpl implements Provider<SOAPMessage> {
> +
> + public SOAPMessage invoke(final SOAPMessage request) {
> + try {
> + System.out.println("=== Received client request ===");
> +
> + // create the SOAPMessage
> + SOAPMessage message = MessageFactory.newInstance().createMessage();
> + SOAPPart part = message.getSOAPPart();
> + SOAPEnvelope envelope = part.getEnvelope();
> + SOAPBody body = envelope.getBody();
> +
> +
> + SOAPBodyElement testResponse = body
> + .addBodyElement(envelope.createName("testXopResponse", null, "http://cxf.apache.org/mime/types"));
> + SOAPElement name = testResponse.addChildElement("name", null, "http://cxf.apache.org/mime/types");
> + name.setTextContent("return detail + call detail");
> + SOAPElement attachinfo = testResponse.addChildElement(
> + "attachinfo", null, "http://cxf.apache.org/mime/types");
> + SOAPElement include = attachinfo.addChildElement("Include", "xop",
> + "http://www.w3.org/2004/08/xop/include");
> +
> + InputStream pre = this.getClass().getResourceAsStream("/wsdl/mtom_xop.wsdl");
> + int fileSize = 0;
> + for (int i = pre.read(); i != -1; i = pre.read()) {
> + fileSize++;
> + }
> +
> + int count = 50;
> + byte[] data = new byte[fileSize * count];
> + for (int x = 0; x < count; x++) {
> + this.getClass().getResourceAsStream("/wsdl/mtom_xop.wsdl").read(data,
> + fileSize * x,
> + fileSize);
> + }
> +
> +
> + DataHandler dh = new DataHandler(new ByteArrayDataSource(data, "application/octet-stream"));
> +
> + // create the image attachment
> + AttachmentPart attachment = message.createAttachmentPart(dh);
> + attachment.setContentId("mtom_xop.wsdl");
> + message.addAttachmentPart(attachment);
> + System.out
> + .println("Adding attachment: " + attachment.getContentId() + ":" + attachment.getSize());
> +
> + // add the reference to the image attachment
> + include.addAttribute(envelope.createName("href"), "cid:" + attachment.getContentId());
> +
> + return message;
> +
> + } catch (Exception e) {
> + e.printStackTrace();
> + }
> + return null;
> + }
> +
> +}
>
> http://git-wip-us.apache.org/repos/asf/cxf/blob/08c9194c/testutils/src/main/resources/wsdl/mtom_xop.wsdl
> ----------------------------------------------------------------------
> diff --git a/testutils/src/main/resources/wsdl/mtom_xop.wsdl b/testutils/src/main/resources/wsdl/mtom_xop.wsdl
> index 02b15fd..b84ded2 100644
> --- a/testutils/src/main/resources/wsdl/mtom_xop.wsdl
> +++ b/testutils/src/main/resources/wsdl/mtom_xop.wsdl
> @@ -96,6 +96,9 @@
> <wsdl:port name="TestMtomPort" binding="tns:TestMtomBinding">
> <soap:address location="http://localhost:9036/mime-test"/>
> </wsdl:port>
> + <wsdl:port name="TestMtomProviderPort" binding="tns:TestMtomBinding">
> + <soap:address location="http://localhost:9036/mime-test-provider"/>
> + </wsdl:port>
> </wsdl:service>
> <wsdl:service name="TestMtomJMSService">
> <wsdl:port name="TestMtomJMSPort" binding="tns:TestMtomBinding">
>
--
Daniel Kulp
dkulp@apache.org - http://dankulp.com/blog
Talend Community Coder - http://coders.talend.com
Re: cxf git commit: [CXF-7269]schemavalidate failed when use mtom and Provider
Posted by Freeman Fang <fr...@gmail.com>.
Sure, done.
Cheers
-------------
Freeman(Yue) Fang
Red Hat, Inc.
FuseSource is now part of Red Hat
> On Mar 7, 2017, at 9:09 PM, Daniel Kulp <dk...@apache.org> wrote:
>
>
> Freeman,
>
> I believe this commit would introduce CXF-7185 for the source data binding…. Can you adjust accordingly? You can see what was done for JAXB.
>
> Thanks!
> Dan
>
>
>
>> On Mar 7, 2017, at 3:44 AM, ffang@apache.org wrote:
>>
>> Repository: cxf
>> Updated Branches:
>> refs/heads/master 09fb22b25 -> 08c9194ce
>>
>>
>> [CXF-7269]schemavalidate failed when use mtom and Provider
>>
>>
>> Project: http://git-wip-us.apache.org/repos/asf/cxf/repo
>> Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/08c9194c
>> Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/08c9194c
>> Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/08c9194c
>>
>> Branch: refs/heads/master
>> Commit: 08c9194ce2c567ebfe59f59ee628b1197d90fd43
>> Parents: 09fb22b
>> Author: Freeman Fang <fr...@gmail.com>
>> Authored: Tue Mar 7 16:44:03 2017 +0800
>> Committer: Freeman Fang <fr...@gmail.com>
>> Committed: Tue Mar 7 16:44:03 2017 +0800
>>
>> ----------------------------------------------------------------------
>> .../databinding/source/XMLStreamDataWriter.java | 111 ++++++++++++++++-
>> .../cxf/systest/mtom/ClientMtomXopTest.java | 119 ++++++++++++++++++-
>> testutils/pom.xml | 4 +
>> .../cxf/mtom_xop/TestMtomProviderImpl.java | 104 ++++++++++++++++
>> testutils/src/main/resources/wsdl/mtom_xop.wsdl | 3 +
>> 5 files changed, 335 insertions(+), 6 deletions(-)
>> ----------------------------------------------------------------------
>>
>>
>> http://git-wip-us.apache.org/repos/asf/cxf/blob/08c9194c/core/src/main/java/org/apache/cxf/databinding/source/XMLStreamDataWriter.java
>> ----------------------------------------------------------------------
>> diff --git a/core/src/main/java/org/apache/cxf/databinding/source/XMLStreamDataWriter.java b/core/src/main/java/org/apache/cxf/databinding/source/XMLStreamDataWriter.java
>> index 8bd9b71..afbcca0 100644
>> --- a/core/src/main/java/org/apache/cxf/databinding/source/XMLStreamDataWriter.java
>> +++ b/core/src/main/java/org/apache/cxf/databinding/source/XMLStreamDataWriter.java
>> @@ -29,12 +29,17 @@ import javax.xml.stream.XMLStreamWriter;
>> import javax.xml.transform.Source;
>> import javax.xml.transform.dom.DOMSource;
>> import javax.xml.validation.Schema;
>> +import javax.xml.validation.Validator;
>>
>> import org.w3c.dom.Document;
>> import org.w3c.dom.DocumentFragment;
>> import org.w3c.dom.Node;
>> +import org.w3c.dom.NodeList;
>>
>> +import org.xml.sax.ErrorHandler;
>> import org.xml.sax.SAXException;
>> +import org.xml.sax.SAXParseException;
>> +
>>
>> import org.apache.cxf.common.i18n.Message;
>> import org.apache.cxf.common.logging.LogUtils;
>> @@ -46,10 +51,16 @@ import org.apache.cxf.staxutils.StaxUtils;
>> import org.apache.cxf.staxutils.W3CDOMStreamWriter;
>>
>> public class XMLStreamDataWriter implements DataWriter<XMLStreamWriter> {
>> +
>> private static final Logger LOG = LogUtils.getL7dLogger(XMLStreamDataWriter.class);
>>
>> +
>> private Schema schema;
>>
>> + public XMLStreamDataWriter() {
>> +
>> + }
>> +
>> public void write(Object obj, MessagePartInfo part, XMLStreamWriter output) {
>> write(obj, output);
>> }
>> @@ -61,7 +72,10 @@ public class XMLStreamDataWriter implements DataWriter<XMLStreamWriter> {
>> DataSource ds = (DataSource)obj;
>> if (schema != null) {
>> DOMSource domSource = new DOMSource(StaxUtils.read(ds.getInputStream()));
>> - schema.newValidator().validate(domSource);
>> + Validator schemaValidator = schema.newValidator();
>> + schemaValidator.setErrorHandler(
>> + new MtomValidationErrorHandler(schemaValidator.getErrorHandler(), domSource.getNode()));
>> + schemaValidator.validate(domSource);
>> StaxUtils.copy(domSource, writer);
>> } else {
>> reader = StaxUtils.createXMLStreamReader(ds.getInputStream());
>> @@ -71,7 +85,10 @@ public class XMLStreamDataWriter implements DataWriter<XMLStreamWriter> {
>>
>> } else if (obj instanceof Node) {
>> if (schema != null) {
>> - schema.newValidator().validate(new DOMSource((Node)obj));
>> + Validator schemaValidator = schema.newValidator();
>> + schemaValidator.setErrorHandler(
>> + new MtomValidationErrorHandler(schemaValidator.getErrorHandler(), (Node)obj));
>> + schemaValidator.validate(new DOMSource((Node)obj));
>> }
>> Node nd = (Node)obj;
>> writeNode(nd, writer);
>> @@ -82,7 +99,10 @@ public class XMLStreamDataWriter implements DataWriter<XMLStreamWriter> {
>> //make the source re-readable.
>> s = new DOMSource(StaxUtils.read(s));
>> }
>> - schema.newValidator().validate(s);
>> + Validator schemaValidator = schema.newValidator();
>> + schemaValidator.setErrorHandler(
>> + new MtomValidationErrorHandler(schemaValidator.getErrorHandler(), ((DOMSource)s).getNode()));
>> + schemaValidator.validate(s);
>> }
>> if (s instanceof DOMSource
>> && ((DOMSource) s).getNode() == null) {
>> @@ -152,5 +172,90 @@ public class XMLStreamDataWriter implements DataWriter<XMLStreamWriter> {
>>
>> public void setProperty(String key, Object value) {
>> }
>> +
>> + private static class MtomValidationErrorHandler implements ErrorHandler {
>> + private ErrorHandler origErrorHandler;
>> + private Node node;
>> +
>> + MtomValidationErrorHandler(ErrorHandler origErrorHandler, Node node) {
>> + this.origErrorHandler = origErrorHandler;
>> + this.node = node;
>> + }
>> +
>> +
>> + @Override
>> + public void warning(SAXParseException exception) throws SAXException {
>> +
>> + if (this.origErrorHandler != null) {
>> + this.origErrorHandler.warning(exception);
>> + } else {
>> + // do nothing
>> + }
>> + }
>> +
>> + @Override
>> + public void error(SAXParseException exception) throws SAXException {
>> + if (this.isCVC312Exception(exception)) {
>> + String elementName = this.getAttachmentElementName(exception);
>> + if (node != null && this.findIncludeNode(node, elementName)) {
>> + return;
>> + }
>> + }
>> +
>> + if (this.origErrorHandler != null) {
>> + this.origErrorHandler.error(exception);
>> + } else {
>> + throw exception;
>> + }
>> +
>> + }
>> +
>> + @Override
>> + public void fatalError(SAXParseException exception) throws SAXException {
>> + if (this.origErrorHandler != null) {
>> + this.origErrorHandler.fatalError(exception);
>> + } else {
>> + throw exception;
>> + }
>> +
>> + }
>> +
>> + private boolean isCVC312Exception(SAXParseException exception) {
>> + String msg = exception.getMessage();
>> + return msg.startsWith("cvc-type.3.1.2: ")
>> + && msg.endsWith("is a simple type, so it must have no element information item [children].");
>> +
>> +
>> + }
>> +
>> + private String getAttachmentElementName(SAXParseException exception) {
>> + String msg = exception.getMessage();
>> + String str[] = msg.split("'");
>> + return str[1];
>> + }
>> +
>> + private boolean findIncludeNode(Node checkNode, String mtomElement) {
>> + boolean ret = false;
>> + NodeList nList = checkNode.getChildNodes();
>> + for (int i = 0; i < nList.getLength(); i++) {
>> + Node nNode = nList.item(i);
>> + if (nNode.getLocalName() != null
>> + && nNode.getLocalName().equals(mtomElement)) {
>> + NodeList subNodeList = nNode.getChildNodes();
>> + for (int j = 0; j < subNodeList.getLength(); j++) {
>> + Node subNode = subNodeList.item(j);
>> + if (subNode.getNamespaceURI().equals("http://www.w3.org/2004/08/xop/include")
>> + && subNode.getLocalName().equals("Include")) {
>> + // This is the Mtom element which break the SchemaValidation so ignore this
>> + return true;
>> + }
>> + }
>> + } else {
>> + ret = findIncludeNode(nNode, mtomElement);
>> + }
>> + }
>> + return ret;
>> + }
>> + }
>>
>> }
>>
>> http://git-wip-us.apache.org/repos/asf/cxf/blob/08c9194c/systests/uncategorized/src/test/java/org/apache/cxf/systest/mtom/ClientMtomXopTest.java
>> ----------------------------------------------------------------------
>> diff --git a/systests/uncategorized/src/test/java/org/apache/cxf/systest/mtom/ClientMtomXopTest.java b/systests/uncategorized/src/test/java/org/apache/cxf/systest/mtom/ClientMtomXopTest.java
>> index 184bd64..1616b48 100644
>> --- a/systests/uncategorized/src/test/java/org/apache/cxf/systest/mtom/ClientMtomXopTest.java
>> +++ b/systests/uncategorized/src/test/java/org/apache/cxf/systest/mtom/ClientMtomXopTest.java
>> @@ -38,10 +38,10 @@ import org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor;
>> import org.apache.cxf.endpoint.Client;
>> import org.apache.cxf.endpoint.ClientImpl;
>> import org.apache.cxf.endpoint.Endpoint;
>> -import org.apache.cxf.ext.logging.LoggingInInterceptor;
>> -import org.apache.cxf.ext.logging.LoggingOutInterceptor;
>> import org.apache.cxf.frontend.ClientProxy;
>> import org.apache.cxf.helpers.IOUtils;
>> +import org.apache.cxf.interceptor.LoggingInInterceptor;
>> +import org.apache.cxf.interceptor.LoggingOutInterceptor;
>> import org.apache.cxf.jaxws.EndpointImpl;
>> import org.apache.cxf.jaxws.JaxWsClientProxy;
>> import org.apache.cxf.jaxws.binding.soap.SOAPBindingImpl;
>> @@ -51,6 +51,7 @@ import org.apache.cxf.message.Message;
>> import org.apache.cxf.mime.TestMtom;
>> import org.apache.cxf.mime.types.XopStringType;
>> import org.apache.cxf.mtom_xop.TestMtomImpl;
>> +import org.apache.cxf.mtom_xop.TestMtomProviderImpl;
>> import org.apache.cxf.service.Service;
>> import org.apache.cxf.service.model.EndpointInfo;
>> import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
>> @@ -63,7 +64,9 @@ import org.junit.Test;
>> public class ClientMtomXopTest extends AbstractBusClientServerTestBase {
>> public static final String PORT = allocatePort(ClientMtomXopTest.class);
>> public static final QName MTOM_PORT = new QName("http://cxf.apache.org/mime", "TestMtomPort");
>> + public static final QName MTOM_PORT_PROVIDER = new QName("http://cxf.apache.org/mime", "TestMtomProviderPort");
>> public static final QName MTOM_SERVICE = new QName("http://cxf.apache.org/mime", "TestMtomService");
>> +
>>
>>
>> public static class Server extends AbstractBusTestServerBase {
>> @@ -71,14 +74,21 @@ public class ClientMtomXopTest extends AbstractBusClientServerTestBase {
>> protected void run() {
>> Object implementor = new TestMtomImpl();
>> String address = "http://localhost:" + PORT + "/mime-test";
>> + String addressProvider = "http://localhost:" + PORT + "/mime-test-provider";
>> try {
>> jaxep = (EndpointImpl) javax.xml.ws.Endpoint.publish(address, implementor);
>> Endpoint ep = jaxep.getServer().getEndpoint();
>> ep.getInInterceptors().add(new TestMultipartMessageInterceptor());
>> ep.getOutInterceptors().add(new TestAttachmentOutInterceptor());
>> -
>> + jaxep.getInInterceptors().add(new LoggingInInterceptor());
>> + jaxep.getOutInterceptors().add(new LoggingOutInterceptor());
>> SOAPBinding jaxWsSoapBinding = (SOAPBinding) jaxep.getBinding();
>> jaxWsSoapBinding.setMTOMEnabled(true);
>> + EndpointImpl endpoint =
>> + (EndpointImpl)javax.xml.ws.Endpoint.publish(addressProvider, new TestMtomProviderImpl());
>> + endpoint.getProperties().put("schema-validation-enabled", "true");
>> + endpoint.getInInterceptors().add(new LoggingInInterceptor());
>> + endpoint.getOutInterceptors().add(new LoggingOutInterceptor());
>>
>> } catch (Exception e) {
>> Thread.currentThread().interrupt();
>> @@ -197,6 +207,109 @@ public class ClientMtomXopTest extends AbstractBusClientServerTestBase {
>> throw ex;
>> }
>> }
>> +
>> +
>> + @Test
>> + public void testMtomXopProvider() throws Exception {
>> + TestMtom mtomPort = createPort(MTOM_SERVICE, MTOM_PORT_PROVIDER, TestMtom.class, true, true);
>> + try {
>> + Holder<DataHandler> param = new Holder<DataHandler>();
>> + Holder<String> name;
>> + byte bytes[];
>> + InputStream in;
>> +
>> + InputStream pre = this.getClass().getResourceAsStream("/wsdl/mtom_xop.wsdl");
>> + int fileSize = 0;
>> + for (int i = pre.read(); i != -1; i = pre.read()) {
>> + fileSize++;
>> + }
>> +
>> + int count = 50;
>> + byte[] data = new byte[fileSize * count];
>> + for (int x = 0; x < count; x++) {
>> + this.getClass().getResourceAsStream("/wsdl/mtom_xop.wsdl").read(data,
>> + fileSize * x,
>> + fileSize);
>> + }
>> +
>> + Object[] validationTypes = new Object[]{Boolean.TRUE, SchemaValidationType.IN, SchemaValidationType.BOTH};
>> +
>> + for (Object validationType : validationTypes) {
>> + ((BindingProvider)mtomPort).getRequestContext().put(Message.SCHEMA_VALIDATION_ENABLED,
>> + validationType);
>> +
>> + param.value = new DataHandler(new ByteArrayDataSource(data, "application/octet-stream"));
>> + name = new Holder<String>("call detail");
>> + mtomPort.testXop(name, param);
>> + assertEquals("name unchanged", "return detail + call detail", name.value);
>> + assertNotNull(param.value);
>> +
>> + in = param.value.getInputStream();
>> + bytes = IOUtils.readBytesFromStream(in);
>> + assertEquals(data.length, bytes.length);
>> + in.close();
>> +
>> + param.value = new DataHandler(new ByteArrayDataSource(data, "application/octet-stream"));
>> + name = new Holder<String>("call detail");
>> + mtomPort.testXop(name, param);
>> + assertEquals("name unchanged", "return detail + call detail", name.value);
>> + assertNotNull(param.value);
>> +
>> + in = param.value.getInputStream();
>> + bytes = IOUtils.readBytesFromStream(in);
>> + assertEquals(data.length, bytes.length);
>> + in.close();
>> + }
>> +
>> + validationTypes = new Object[]{Boolean.FALSE, SchemaValidationType.OUT, SchemaValidationType.NONE};
>> + for (Object validationType : validationTypes) {
>> + ((BindingProvider)mtomPort).getRequestContext().put(Message.SCHEMA_VALIDATION_ENABLED,
>> + validationType);
>> + SAAJOutInterceptor saajOut = new SAAJOutInterceptor();
>> + SAAJInInterceptor saajIn = new SAAJInInterceptor();
>> +
>> + param.value = new DataHandler(new ByteArrayDataSource(data, "application/octet-stream"));
>> + name = new Holder<String>("call detail");
>> + mtomPort.testXop(name, param);
>> + assertEquals("name unchanged", "return detail + call detail", name.value);
>> + assertNotNull(param.value);
>> +
>> + in = param.value.getInputStream();
>> + bytes = IOUtils.readBytesFromStream(in);
>> + assertEquals(data.length, bytes.length);
>> + in.close();
>> +
>> + ClientProxy.getClient(mtomPort).getInInterceptors().add(saajIn);
>> + ClientProxy.getClient(mtomPort).getInInterceptors().add(saajOut);
>> + param.value = new DataHandler(new ByteArrayDataSource(data, "application/octet-stream"));
>> + name = new Holder<String>("call detail");
>> + mtomPort.testXop(name, param);
>> + assertEquals("name unchanged", "return detail + call detail", name.value);
>> + assertNotNull(param.value);
>> +
>> + in = param.value.getInputStream();
>> + bytes = IOUtils.readBytesFromStream(in);
>> + assertEquals(data.length, bytes.length);
>> + in.close();
>> +
>> + ClientProxy.getClient(mtomPort).getInInterceptors().remove(saajIn);
>> + ClientProxy.getClient(mtomPort).getInInterceptors().remove(saajOut);
>> + }
>> + } catch (UndeclaredThrowableException ex) {
>> + throw (Exception)ex.getCause();
>> + } catch (Exception ex) {
>> + if (ex.getMessage().contains("Connection reset")
>> + && System.getProperty("java.specification.version", "1.5").contains("1.6")) {
>> + //There seems to be a bug/interaction with Java 1.6 and Jetty where
>> + //Jetty will occasionally send back a RST prior to all the data being
>> + //sent back to the client when using localhost (which is what we do)
>> + //we'll ignore for now
>> + return;
>> + }
>> + System.out.println(System.getProperties());
>> + throw ex;
>> + }
>> + }
>>
>> @Test
>> public void testMtomWithFileName() throws Exception {
>>
>> http://git-wip-us.apache.org/repos/asf/cxf/blob/08c9194c/testutils/pom.xml
>> ----------------------------------------------------------------------
>> diff --git a/testutils/pom.xml b/testutils/pom.xml
>> index bd481ad..fb611ab 100644
>> --- a/testutils/pom.xml
>> +++ b/testutils/pom.xml
>> @@ -53,6 +53,10 @@
>> <optional>true</optional>
>> </dependency>
>> <dependency>
>> + <groupId>org.apache.geronimo.specs</groupId>
>> + <artifactId>geronimo-javamail_1.4_spec</artifactId>
>> + </dependency>
>> + <dependency>
>> <groupId>org.springframework</groupId>
>> <artifactId>spring-core</artifactId>
>> <scope>provided</scope>
>>
>> http://git-wip-us.apache.org/repos/asf/cxf/blob/08c9194c/testutils/src/main/java/org/apache/cxf/mtom_xop/TestMtomProviderImpl.java
>> ----------------------------------------------------------------------
>> diff --git a/testutils/src/main/java/org/apache/cxf/mtom_xop/TestMtomProviderImpl.java b/testutils/src/main/java/org/apache/cxf/mtom_xop/TestMtomProviderImpl.java
>> new file mode 100644
>> index 0000000..de1f442
>> --- /dev/null
>> +++ b/testutils/src/main/java/org/apache/cxf/mtom_xop/TestMtomProviderImpl.java
>> @@ -0,0 +1,104 @@
>> +/**
>> + * 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.cxf.mtom_xop;
>> +
>> +import java.io.InputStream;
>> +
>> +import javax.activation.DataHandler;
>> +import javax.mail.util.ByteArrayDataSource;
>> +import javax.xml.soap.AttachmentPart;
>> +import javax.xml.soap.MessageFactory;
>> +import javax.xml.soap.SOAPBody;
>> +import javax.xml.soap.SOAPBodyElement;
>> +import javax.xml.soap.SOAPElement;
>> +import javax.xml.soap.SOAPEnvelope;
>> +import javax.xml.soap.SOAPMessage;
>> +import javax.xml.soap.SOAPPart;
>> +import javax.xml.ws.Provider;
>> +import javax.xml.ws.Service.Mode;
>> +import javax.xml.ws.ServiceMode;
>> +import javax.xml.ws.WebServiceProvider;
>> +import javax.xml.ws.soap.MTOM;
>> +
>> +
>> +@WebServiceProvider(portName = "TestMtomProviderPort",
>> +serviceName = "TestMtomService",
>> +targetNamespace = "http://cxf.apache.org/mime",
>> +wsdlLocation = "testutils/mtom_xop.wsdl")
>> +@ServiceMode(value = Mode.MESSAGE)
>> +@MTOM
>> +
>> +public class TestMtomProviderImpl implements Provider<SOAPMessage> {
>> +
>> + public SOAPMessage invoke(final SOAPMessage request) {
>> + try {
>> + System.out.println("=== Received client request ===");
>> +
>> + // create the SOAPMessage
>> + SOAPMessage message = MessageFactory.newInstance().createMessage();
>> + SOAPPart part = message.getSOAPPart();
>> + SOAPEnvelope envelope = part.getEnvelope();
>> + SOAPBody body = envelope.getBody();
>> +
>> +
>> + SOAPBodyElement testResponse = body
>> + .addBodyElement(envelope.createName("testXopResponse", null, "http://cxf.apache.org/mime/types"));
>> + SOAPElement name = testResponse.addChildElement("name", null, "http://cxf.apache.org/mime/types");
>> + name.setTextContent("return detail + call detail");
>> + SOAPElement attachinfo = testResponse.addChildElement(
>> + "attachinfo", null, "http://cxf.apache.org/mime/types");
>> + SOAPElement include = attachinfo.addChildElement("Include", "xop",
>> + "http://www.w3.org/2004/08/xop/include");
>> +
>> + InputStream pre = this.getClass().getResourceAsStream("/wsdl/mtom_xop.wsdl");
>> + int fileSize = 0;
>> + for (int i = pre.read(); i != -1; i = pre.read()) {
>> + fileSize++;
>> + }
>> +
>> + int count = 50;
>> + byte[] data = new byte[fileSize * count];
>> + for (int x = 0; x < count; x++) {
>> + this.getClass().getResourceAsStream("/wsdl/mtom_xop.wsdl").read(data,
>> + fileSize * x,
>> + fileSize);
>> + }
>> +
>> +
>> + DataHandler dh = new DataHandler(new ByteArrayDataSource(data, "application/octet-stream"));
>> +
>> + // create the image attachment
>> + AttachmentPart attachment = message.createAttachmentPart(dh);
>> + attachment.setContentId("mtom_xop.wsdl");
>> + message.addAttachmentPart(attachment);
>> + System.out
>> + .println("Adding attachment: " + attachment.getContentId() + ":" + attachment.getSize());
>> +
>> + // add the reference to the image attachment
>> + include.addAttribute(envelope.createName("href"), "cid:" + attachment.getContentId());
>> +
>> + return message;
>> +
>> + } catch (Exception e) {
>> + e.printStackTrace();
>> + }
>> + return null;
>> + }
>> +
>> +}
>>
>> http://git-wip-us.apache.org/repos/asf/cxf/blob/08c9194c/testutils/src/main/resources/wsdl/mtom_xop.wsdl
>> ----------------------------------------------------------------------
>> diff --git a/testutils/src/main/resources/wsdl/mtom_xop.wsdl b/testutils/src/main/resources/wsdl/mtom_xop.wsdl
>> index 02b15fd..b84ded2 100644
>> --- a/testutils/src/main/resources/wsdl/mtom_xop.wsdl
>> +++ b/testutils/src/main/resources/wsdl/mtom_xop.wsdl
>> @@ -96,6 +96,9 @@
>> <wsdl:port name="TestMtomPort" binding="tns:TestMtomBinding">
>> <soap:address location="http://localhost:9036/mime-test"/>
>> </wsdl:port>
>> + <wsdl:port name="TestMtomProviderPort" binding="tns:TestMtomBinding">
>> + <soap:address location="http://localhost:9036/mime-test-provider"/>
>> + </wsdl:port>
>> </wsdl:service>
>> <wsdl:service name="TestMtomJMSService">
>> <wsdl:port name="TestMtomJMSPort" binding="tns:TestMtomBinding">
>>
>
> --
> Daniel Kulp
> dkulp@apache.org - http://dankulp.com/blog
> Talend Community Coder - http://coders.talend.com
>