Author: scheu Date: Tue Sep 21 16:48:06 2010 New Revision: 999495 URL: http://svn.apache.org/viewvc?rev=999495&view=rev Log: Axis2-4826 Contributor: Rich Scheuerle Summary: Wrote a test to isolate a problem with the marshaller embedding a BOM within a message. Provided a fix to the marshaling code to detect this situation and skip the BOM.
Added: axis/axis2/java/core/trunk/modules/jaxws/src/org/apache/axis2/datasource/jaxb/BOMOutputStreamFilter.java Modified: axis/axis2/java/core/trunk/modules/jaxws-integration/test/org/apache/axis2/jaxws/jaxb/string/JAXBStringUTF16Tests.java axis/axis2/java/core/trunk/modules/jaxws-integration/test/org/apache/axis2/jaxws/provider/stringmsg/StringMessageProvider.java axis/axis2/java/core/trunk/modules/jaxws/src/org/apache/axis2/datasource/jaxb/JAXBDSContext.java Modified: axis/axis2/java/core/trunk/modules/jaxws-integration/test/org/apache/axis2/jaxws/jaxb/string/JAXBStringUTF16Tests.java URL: http://svn.apache.org/viewvc/axis/axis2/java/core/trunk/modules/jaxws-integration/test/org/apache/axis2/jaxws/jaxb/string/JAXBStringUTF16Tests.java?rev=999495&r1=999494&r2=999495&view=diff ============================================================================== --- axis/axis2/java/core/trunk/modules/jaxws-integration/test/org/apache/axis2/jaxws/jaxb/string/JAXBStringUTF16Tests.java (original) +++ axis/axis2/java/core/trunk/modules/jaxws-integration/test/org/apache/axis2/jaxws/jaxb/string/JAXBStringUTF16Tests.java Tue Sep 21 16:48:06 2010 @@ -10,6 +10,7 @@ import javax.xml.ws.WebServiceException; public class JAXBStringUTF16Tests extends AbstractTestCase { String axisEndpoint = "http://localhost:6060/axis2/services/JAXBStringService.JAXBStringPortTypeImplPort"; + String axis2ProviderEndpoint = "http://localhost:6060/axis2/services/StringMessageProviderService.StringMessageProviderPort"; public static Test suite() { return getTestSetup(new TestSuite(JAXBStringUTF16Tests.class)); @@ -22,6 +23,12 @@ public class JAXBStringUTF16Tests extend private void runTest16(String value, String value1) { runTestWithUTF16(value, value1); } + + public void testSimpleString16BOM() throws Exception { + // Call the Axis2 StringMessageProvider which has a check to ensure + // that the BOM for UTF-16 is not written inside the message. + runTestWithEncoding("a simple string", "a simple string", "UTF-16", axis2ProviderEndpoint); + } public void testSimpleString16() throws Exception { runTest16("a simple string"); @@ -76,13 +83,15 @@ public class JAXBStringUTF16Tests extend private void runTestWithUTF16(String input, String output) { runTestWithEncoding(input, output, "UTF-16"); } - private void runTestWithEncoding(String input, String output, String encoding) { + runTestWithEncoding(input, output, encoding, axisEndpoint); + } + private void runTestWithEncoding(String input, String output, String encoding, String endpoint) { TestLogger.logger.debug("Test : " + getName()); try { JAXBStringPortType myPort = (new JAXBStringService()).getJAXBStringPort(); BindingProvider p = (BindingProvider) myPort; - p.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, axisEndpoint); + p.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpoint); if (encoding != null) { p.getRequestContext().put(org.apache.axis2.Constants.Configuration.CHARACTER_SET_ENCODING, encoding); Modified: axis/axis2/java/core/trunk/modules/jaxws-integration/test/org/apache/axis2/jaxws/provider/stringmsg/StringMessageProvider.java URL: http://svn.apache.org/viewvc/axis/axis2/java/core/trunk/modules/jaxws-integration/test/org/apache/axis2/jaxws/provider/stringmsg/StringMessageProvider.java?rev=999495&r1=999494&r2=999495&view=diff ============================================================================== --- axis/axis2/java/core/trunk/modules/jaxws-integration/test/org/apache/axis2/jaxws/provider/stringmsg/StringMessageProvider.java (original) +++ axis/axis2/java/core/trunk/modules/jaxws-integration/test/org/apache/axis2/jaxws/provider/stringmsg/StringMessageProvider.java Tue Sep 21 16:48:06 2010 @@ -23,12 +23,15 @@ import org.apache.axis2.jaxws.TestLogger import javax.xml.ws.BindingType; import javax.xml.ws.Provider; +import javax.xml.ws.Service; +import javax.xml.ws.ServiceMode; import javax.xml.ws.WebServiceProvider; import javax.xml.ws.soap.SOAPBinding; import javax.xml.ws.http.HTTPBinding; @WebServiceProvider(serviceName="StringMessageProviderService") @BindingType(SOAPBinding.SOAP11HTTP_BINDING) +...@servicemode(value=Service.Mode.MESSAGE) public class StringMessageProvider implements Provider<String> { private static String responseGood = "<?xml version='1.0' encoding='utf-8'?><soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"><soapenv:Header /><soapenv:Body><provider><message>request processed</message></provider></soapenv:Body></soapenv:Envelope>"; private static String responseBad = "<?xml version='1.0' encoding='utf-8'?><soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"><soapenv:Header /><soapenv:Body><provider><message>ERROR:null request received</message><provider></soapenv:Body></soapenv:Envelope>"; @@ -39,6 +42,22 @@ public class StringMessageProvider imple TestLogger.logger.debug(">> StringMessageProvider received a new request"); TestLogger.logger.debug(">> request [" + str + "]"); + // Make sure there are no extra characters (like a BOM) between the Body tag and the operation element + if (str.contains("echo")) { + if (str.contains("Body><echo")) { + // Good data...replace the echo with echoResponse + TestLogger.logger.debug("Valid"); + str = str.replaceAll("echo", "echoResponse"); + str = str.replaceAll("arg", "response"); + return str; + + } else { + TestLogger.logger.debug("Bad Data detected after the SOAP body"); + // Bad data...simply return the bad response..this will cause an exception on the client + return responseBad; + } + } + return responseGood; } TestLogger.logger.debug(">> ERROR:null request received"); Added: axis/axis2/java/core/trunk/modules/jaxws/src/org/apache/axis2/datasource/jaxb/BOMOutputStreamFilter.java URL: http://svn.apache.org/viewvc/axis/axis2/java/core/trunk/modules/jaxws/src/org/apache/axis2/datasource/jaxb/BOMOutputStreamFilter.java?rev=999495&view=auto ============================================================================== --- axis/axis2/java/core/trunk/modules/jaxws/src/org/apache/axis2/datasource/jaxb/BOMOutputStreamFilter.java (added) +++ axis/axis2/java/core/trunk/modules/jaxws/src/org/apache/axis2/datasource/jaxb/BOMOutputStreamFilter.java Tue Sep 21 16:48:06 2010 @@ -0,0 +1,112 @@ +/* + * 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.axis2.datasource.jaxb; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Strip off the BOM when serializing an embedded JAXB object. + */ +public class BOMOutputStreamFilter extends FilterOutputStream { + + private static final Log log = LogFactory.getLog(BOMOutputStreamFilter.class); + int count = 0; + int bomLength = 2; + + + /** + * Create a BOMOutputStreamFilter to remove the BOM (Byte Order Mark) + * @param encoding + * @param out + */ + public BOMOutputStreamFilter(String encoding, OutputStream out) { + super(out); + if (encoding == null || encoding.equalsIgnoreCase("UTF-8")) { + bomLength = 0; + } else if (encoding.equalsIgnoreCase("UTF-16") || + encoding.equalsIgnoreCase("UTF-16LE") || + encoding.equalsIgnoreCase("UTF-16LE")) { + bomLength = 2; // FF FE or FE FF + } else if (encoding.equalsIgnoreCase("UTF-32")) { + // Currently not valid for SOAP...adding for completeness + bomLength = 4; // 00 00 FE FF or FF FE 00 00 + } else { + + bomLength = 0; + if (log.isDebugEnabled()) { + log.debug("Don't know the BOM length for " + encoding + ". assuming zero."); + } + } + if (log.isDebugEnabled()) { + log.debug("encoding = " + encoding); + log.debug("expected BOM length = " + bomLength); + } + } + + + @Override + public void write(int b) throws IOException { + // Don't write the first two characters because they represent the BOM + if (count >= bomLength) { + out.write(b); + } else { + if (b == 0 || // 0x00 + b == -1 || // 0xFF + b == -2) { // 0xFE + // skip...this is a BOM character + if (log.isDebugEnabled()) { + log.debug("Skipping BOM character " + b); + } + } else { + out.write(b); + } + } + count++; + } + + @Override + public void write(byte[] b) throws IOException { + // Delegate to our 3 argument write method + this.write(b, 0, b.length); + } + + + @Override + public void write(byte[] b, int off, int len) throws IOException { + // Delegate the first couple of bytes to our + // single argument write constructor + while (count < bomLength && len > 0) { + this.write(b[off]); + off++; + len--; + } + + // Delegate the remaining bytes to the target output stream + if (len > 0) { + out.write(b, off, len); + count = count + len; + } + } +} + Modified: axis/axis2/java/core/trunk/modules/jaxws/src/org/apache/axis2/datasource/jaxb/JAXBDSContext.java URL: http://svn.apache.org/viewvc/axis/axis2/java/core/trunk/modules/jaxws/src/org/apache/axis2/datasource/jaxb/JAXBDSContext.java?rev=999495&r1=999494&r2=999495&view=diff ============================================================================== --- axis/axis2/java/core/trunk/modules/jaxws/src/org/apache/axis2/datasource/jaxb/JAXBDSContext.java (original) +++ axis/axis2/java/core/trunk/modules/jaxws/src/org/apache/axis2/datasource/jaxb/JAXBDSContext.java Tue Sep 21 16:48:06 2010 @@ -45,6 +45,7 @@ import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; +import javax.xml.bind.PropertyException; import javax.xml.bind.Unmarshaller; import javax.xml.bind.attachment.AttachmentMarshaller; import javax.xml.bind.attachment.AttachmentUnmarshaller; @@ -479,7 +480,7 @@ public class JAXBDSContext { // XMLStreamWriter. // Take advantage of this optimization if there is an output stream. try { - OutputStream os = (optimize) ? getOutputStream(writer) : null; + OutputStream os = (optimize) ? getOutputStream(writer,m) : null; if (os != null) { if (DEBUG_ENABLED) { log.debug("Invoking marshalByElement. " + @@ -524,9 +525,11 @@ public class JAXBDSContext { /** * If the writer is backed by an OutputStream, then return the OutputStream * @param writer + * @param Marshaller * @return OutputStream or null */ - private static OutputStream getOutputStream(XMLStreamWriter writer) throws XMLStreamException { + private static OutputStream getOutputStream(XMLStreamWriter writer, + Marshaller m) throws XMLStreamException { if (log.isDebugEnabled()) { log.debug("XMLStreamWriter is " + writer); } @@ -543,6 +546,23 @@ public class JAXBDSContext { log.debug("OutputStream accessible from XMLStreamWriterWithOS is " + os); } } + if (os != null) { + String marshallerEncoding = null; + try { + marshallerEncoding = (String) m.getProperty(Marshaller.JAXB_ENCODING); + } catch (PropertyException e) { + if (DEBUG_ENABLED) { + log.debug("Could not query JAXB_ENCODING..Continuing. " + e); + } + } + if (marshallerEncoding != null && !marshallerEncoding.equalsIgnoreCase("UTF-8")) { + if (DEBUG_ENABLED) { + log.debug("Wrapping output stream to remove BOM"); + } + os = new BOMOutputStreamFilter(marshallerEncoding, os); + } + } + return os; } @@ -951,7 +971,7 @@ public class JAXBDSContext { } // If the output stream is available, marshal directly to it - OutputStream os = (optimize) ? getOutputStream(writer) : null; + OutputStream os = (optimize) ? getOutputStream(writer, m) : null; if (os == null){ if (DEBUG_ENABLED) { log.debug("Invoking marshalByType. " +