Index: src/test/java/org/castor/util/TestHex.java =================================================================== --- src/test/java/org/castor/util/TestHex.java (Revision 0) +++ src/test/java/org/castor/util/TestHex.java (Revision 0) @@ -0,0 +1,46 @@ +/* + * Copyright 2007 Werner Guttmann + * + * Licensed 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.castor.util; + +import java.util.Arrays; +import java.util.Random; + +import junit.framework.TestCase; + +/** + * JUnit test case for HEX en-/coding. + * @author Johan Lindquist + * @version $Revision$ + */ +public class TestHex extends TestCase { + + public void testEncodeDecode() { + byte[] bytes = new byte[256]; + + new Random(1L).nextBytes(bytes); + final String encodedBytes = Hex.encode(bytes); + assertEquals( + "Bad encoded data", + "73d51abbd89cb8196f0efb6892f94d68fccc2c35f0b84609e5f12c55dd85aba8d5d9bef76808f3b572e5900112b81927ba5bb5f67e1bda28b4049bf0e4aed78db15d7bf2fc0c34e9a99de4ef3bc2b17c8137ad659878f9e93df1f658367aca286452474b9ef3765e24e9a88173724dddfb04b01dcceb0c8aead641c58dad569581baeea87c10d40a47902028e61cfdc243d9d16008aabc9fb77cc723a56017e14f1ce8b1698341734a6823ce02043e016b544901214a2ddab82fec85c0b9fe0549c475be5b887bb4b8995b24fb5c6846f88b527b4f9d4c1391f1678b23ba4f9c9cd7bc93eb5776f4f03675344864294661c5949faf17b130fcf6482f971a5500", + encodedBytes); + + final byte[] decodedBytes = Hex.decode(encodedBytes); + + assertTrue("Bad decoded bytes", Arrays.equals(bytes, decodedBytes)); + + } + +} Index: src/bugs/xml/bug423/mapping.xml =================================================================== --- src/bugs/xml/bug423/mapping.xml (Revision 0) +++ src/bugs/xml/bug423/mapping.xml (Revision 0) @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: src/bugs/xml/bug423/README.txt =================================================================== --- src/bugs/xml/bug423/README.txt (Revision 0) +++ src/bugs/xml/bug423/README.txt (Revision 0) @@ -0,0 +1,3 @@ +bug number: 423 +description: Handling of hex binary / base64 encoding +castor: version trunk Index: src/bugs/xml/bug423/TestTemplate.java =================================================================== --- src/bugs/xml/bug423/TestTemplate.java (Revision 0) +++ src/bugs/xml/bug423/TestTemplate.java (Revision 0) @@ -0,0 +1,90 @@ +package xml.bug423; + +import java.io.FileWriter; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Arrays; + +import junit.framework.TestCase; + +import org.exolab.castor.mapping.Mapping; +import org.exolab.castor.xml.Marshaller; +import org.exolab.castor.xml.Unmarshaller; +import org.xml.sax.InputSource; + +/** + * @author Johan Lindquist + */ +public final class TestTemplate extends TestCase { + + private static final String SAMPLE_FILE = "entity.xml"; + private static final String MAPPING_FILE = "mapping.xml"; + + private static final byte[] EXPECTED_HEX_ELEMENT_ARRAY = new byte[]{0x01, 0x04, 0x03, 0x02, 0x01, 0x01}; + private static final byte[] EXPECTED_HEX_ATTRIBUTE_ARRAY = new byte[]{0x01, 0x04, 0x03, 0x02, 0x01, 0x02}; + private static final byte[] EXPECTED_BASE64_ELEMENT_ARRAY = new byte[]{104, -55, 0, 0}; + private static final byte[] EXPECTED_BASE64_ATTRIBUTE_ARRAY = new byte[]{104, -55, 0, 0}; + private static final String EXPECTED_XML = "\n" + + "" + + "aMkAAA==" + + "010403020101" + + ""; + + + public TestTemplate() { + super(); + } + + public TestTemplate(final String name) { + super(name); + } + + /** + * Test method. + * @throws Exception For any exception thrown. + */ + public void testUnmarshalEntity() throws Exception { + + Mapping mapping = new Mapping(); + mapping.loadMapping(getClass().getResource(MAPPING_FILE).toExternalForm()); + + Unmarshaller unmarshaller = new Unmarshaller (Entity.class); + unmarshaller.setMapping(mapping); + + Entity entity = (Entity) unmarshaller.unmarshal(new InputSource(getClass().getResource(SAMPLE_FILE).toExternalForm())); + + assertNotNull (entity); + + assertTrue("Invalid hex value in element", Arrays.equals(EXPECTED_HEX_ELEMENT_ARRAY,entity.getElementHex())); + assertTrue("Invalid hex value in attribute", Arrays.equals(EXPECTED_HEX_ATTRIBUTE_ARRAY, entity.getAttributeHex())); + assertTrue("Invalid base64 value in element", Arrays.equals(EXPECTED_BASE64_ELEMENT_ARRAY, entity.getElementBase64())); + assertTrue("Invalid base64 value in attribute", Arrays.equals(EXPECTED_BASE64_ATTRIBUTE_ARRAY, entity.getAttributeBase64())); + } + + /** + * Test method. + * @throws Exception For any exception thrown. + */ + public void testMarshalEntity() throws Exception { + Mapping mapping = new Mapping(); + mapping.loadMapping(getClass().getResource(MAPPING_FILE).toExternalForm()); + + StringWriter stringWriter = new StringWriter(); + Marshaller marshaller = new Marshaller (stringWriter); + marshaller.setMapping(mapping); + + Entity entity = new Entity(); + entity.setElementHex(EXPECTED_HEX_ELEMENT_ARRAY); + entity.setAttributeHex(EXPECTED_HEX_ATTRIBUTE_ARRAY); + entity.setElementBase64(EXPECTED_BASE64_ELEMENT_ARRAY); + entity.setAttributeBase64(EXPECTED_BASE64_ATTRIBUTE_ARRAY); + marshaller.setEncoding("UTF-8"); + marshaller.marshal(entity); + + String xml = stringWriter.toString(); + System.out.println(xml.toString()); + assertEquals("XML marshalling produced invalid value",EXPECTED_XML.trim(),xml.trim()); + + } + +} Index: src/bugs/xml/bug423/entity.xml =================================================================== --- src/bugs/xml/bug423/entity.xml (Revision 0) +++ src/bugs/xml/bug423/entity.xml (Revision 0) @@ -0,0 +1,5 @@ + + + 010403020101 + aMkAAA== + Index: src/bugs/xml/bug423/Entity.java =================================================================== --- src/bugs/xml/bug423/Entity.java (Revision 0) +++ src/bugs/xml/bug423/Entity.java (Revision 0) @@ -0,0 +1,49 @@ +package xml.bug423; + +public final class Entity { + + private byte[] _attributeHex; + private byte[] _attributeBase64; + private byte[] _elementHex; + private byte[] _elementBase64; + + public byte[] getAttributeHex() + { + return _attributeHex; + } + + public void setAttributeHex(final byte[] attributeHex) + { + _attributeHex = attributeHex; + } + + public byte[] getAttributeBase64() + { + return _attributeBase64; + } + + public void setAttributeBase64(final byte[] attributeBase64) + { + _attributeBase64 = attributeBase64; + } + + public byte[] getElementHex() + { + return _elementHex; + } + + public void setElementHex(final byte[] elementHex) + { + _elementHex = elementHex; + } + + public byte[] getElementBase64() + { + return _elementBase64; + } + + public void setElementBase64(final byte[] elementBase64) + { + _elementBase64 = elementBase64; + } +} Index: src/main/java/org/exolab/castor/xml/UnmarshalHandler.java =================================================================== --- src/main/java/org/exolab/castor/xml/UnmarshalHandler.java (Revision 6910) +++ src/main/java/org/exolab/castor/xml/UnmarshalHandler.java (Arbeitskopie) @@ -50,12 +50,14 @@ //-- Castor imports import org.castor.mapping.BindingType; import org.castor.util.Base64Decoder; +import org.castor.util.Hex; import org.exolab.castor.util.Configuration; import org.exolab.castor.util.ObjectFactory; import org.exolab.castor.util.DefaultObjectFactory; import org.exolab.castor.xml.descriptors.PrimitivesClassDescriptor; import org.exolab.castor.xml.descriptors.StringClassDescriptor; import org.exolab.castor.xml.util.*; +import org.exolab.castor.builder.types.XSHexBinary; import org.exolab.castor.mapping.ClassDescriptor; import org.exolab.castor.mapping.ExtendedFieldHandler; import org.exolab.castor.mapping.FieldHandler; @@ -801,8 +803,12 @@ if (str == null) state.object = new byte[0]; else { - //-- Base64 decoding - state.object = Base64Decoder.decode(str); + //-- Base64/HexBinary decoding + if (XSHexBinary.NAME.equals(descriptor.getSchemaType())) { + state.object = Hex.decode(str); + } else { + state.object = Base64Decoder.decode(str); + } } } else if (state.args != null) { @@ -827,10 +833,14 @@ value = toPrimitiveObject(cdesc.getFieldType(), (String)value, state.fieldDesc); else { Class valueType = cdesc.getFieldType(); - //-- handle base64 + //-- handle base64/hexBinary if (valueType.isArray() && (valueType.getComponentType() == Byte.TYPE)) { - value = Base64Decoder.decode((String) value); + if (XSHexBinary.NAME.equals(descriptor.getSchemaType())) { + value = Hex.decode((String) value); + } else { + value = Base64Decoder.decode((String) value); + } } } @@ -3024,8 +3034,12 @@ if (attValue == null) value = new byte[0]; else { - //-- Base64 decoding - value = Base64Decoder.decode(attValue); + //-- Base64/hexbinary decoding + if (XSHexBinary.NAME.equals(descriptor.getSchemaType())) { + value = Hex.decode(attValue); + } else { + value = Base64Decoder.decode(attValue); + } } } Index: src/main/java/org/exolab/castor/xml/Marshaller.java =================================================================== --- src/main/java/org/exolab/castor/xml/Marshaller.java (Revision 6910) +++ src/main/java/org/exolab/castor/xml/Marshaller.java (Arbeitskopie) @@ -55,6 +55,8 @@ import org.castor.mapping.MappingUnmarshaller; import org.castor.util.Base64Encoder; import org.castor.util.Messages; +import org.castor.util.Hex; +import org.exolab.castor.builder.types.XSHexBinary; import org.exolab.castor.mapping.CollectionHandler; import org.exolab.castor.mapping.MapItem; import org.exolab.castor.mapping.Mapping; @@ -1636,8 +1638,13 @@ char[] chars = null; Class objType = obj.getClass(); if (objType.isArray() && (objType.getComponentType() == Byte.TYPE)) { - //-- handle base64 content - chars = Base64Encoder.encode((byte[]) obj); + //-- handle base64/hexbinary content + final String schemaType = descriptor.getSchemaType(); + if (XSHexBinary.NAME.equals(schemaType)) { + chars = new String(Hex.encode((byte[]) obj)).toCharArray(); + } else { + chars = Base64Encoder.encode((byte[]) obj); + } } else { //-- all other types String str = obj.toString(); @@ -1670,11 +1677,17 @@ } // special case for byte[] else if (byteArray) { - //-- Base64Encoding - char[] chars = Base64Encoder.encode((byte[]) object); + //-- Base64Encoding / HexBinary + String schemaType = descriptor.getSchemaType(); + char[] chars = new char[0]; + if (XSHexBinary.NAME.equals(schemaType)) { + chars = new String(Hex.encode((byte[]) object)).toCharArray(); + } else { + chars = Base64Encoder.encode((byte[]) object); + } try { handler.characters(chars, 0, chars.length); - } catch (org.xml.sax.SAXException sx) { + } catch (org.xml.sax.SAXException sx) { throw new MarshalException(sx); } } @@ -2367,7 +2380,12 @@ //-- handle base64 content Class objType = value.getClass(); if (objType.isArray() && (objType.getComponentType() == Byte.TYPE)) { - value = Base64Encoder.encode((byte[]) value); + final String schemaType = attDescriptor.getSchemaType(); + if (XSHexBinary.NAME.equals(schemaType)) { + value = new String(Hex.encode((byte[]) value)); + } else { + value = new String(Base64Encoder.encode((byte[]) value)); + } } } Index: src/main/java/org/castor/util/Hex.java =================================================================== --- src/main/java/org/castor/util/Hex.java (Revision 0) +++ src/main/java/org/castor/util/Hex.java (Revision 0) @@ -0,0 +1,232 @@ +/* + * Copyright 2007 Werner Guttmann + * + * Licensed 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.castor.util; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.ByteArrayOutputStream; + +/** + * Hex encoder/decoder implementation (borrowed from BouncyCastle=. + * + * @author Johan Lindquist + * @since 1.1.1 + * @version $Revision$ + */ +public final class Hex { + + /** + * Initial size of the decoding table. + */ + private static final int DECODING_TABLE_SIZE = 128; + + /** + * Encoding table. + */ + protected static final byte[] ENCODING_TABLE = {(byte) '0', (byte) '1', + (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', + (byte) '7', (byte) '8', (byte) '9', (byte) 'a', (byte) 'b', + (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f' }; + + /** + * Decoding table. + */ + protected static final byte[] DECODING_TABLE = new byte[DECODING_TABLE_SIZE]; + + /** + * Initialize the decoding table. + */ + protected static void initialiseDecodingTable() { + for (int i = 0; i < ENCODING_TABLE.length; i++) { + DECODING_TABLE[ENCODING_TABLE[i]] = (byte) i; + } + + DECODING_TABLE['A'] = DECODING_TABLE['a']; + DECODING_TABLE['B'] = DECODING_TABLE['b']; + DECODING_TABLE['C'] = DECODING_TABLE['c']; + DECODING_TABLE['D'] = DECODING_TABLE['d']; + DECODING_TABLE['E'] = DECODING_TABLE['e']; + DECODING_TABLE['F'] = DECODING_TABLE['f']; + } + + static { + initialiseDecodingTable(); + } + + /** + * Creates an instance of this class. + */ + private Hex() { + // Nothing to do ... + } + + /** + * Encodes the input data producing a Hex output stream. + * @param data The input data to be HEX encoded + * @param off Initiak offset + * @param length Initial length of the input data array + * @param out The {@link OutputStream} instance holding the encoded input data. + * @return the number of bytes produced. + * @throws IOException If encoding fails. + */ + public static int encode(final byte[] data, final int off, final int length, + final OutputStream out) throws IOException { + for (int i = off; i < (off + length); i++) { + int v = data[i] & 0xff; + + out.write(ENCODING_TABLE[(v >>> 4)]); + out.write(ENCODING_TABLE[v & 0xf]); + } + + return length * 2; + } + + /** + * Indicates whether a given character should be ignored during en-/decoding. + * @param c The character at question. + * @return True if the given character should be ignored. + */ + private static boolean ignore(final char c) { + return (c == '\n' || c == '\r' || c == '\t' || c == ' '); + } + + /** + * Decodes the Hex encoded byte data writing it to the given output stream, + * whitespace characters will be ignored. + * @param data The data to be encoded + * @param off Initial offset. + * @param length Initial length + * @param out The {@link OutputStream} instance + * @return the number of bytes produced. + * @throws IOException If encoding failed. + */ + public static int decode(final byte[] data, final int off, final int length, + final OutputStream out) throws IOException { + byte b1, b2; + int outLen = 0; + + int end = off + length; + + while (end > off) { + if (!ignore((char) data[end - 1])) { + break; + } + + end--; + } + + int i = off; + while (i < end) { + while (i < end && ignore((char) data[i])) { + i++; + } + + b1 = DECODING_TABLE[data[i++]]; + + while (i < end && ignore((char) data[i])) { + i++; + } + + b2 = DECODING_TABLE[data[i++]]; + + out.write((b1 << 4) | b2); + + outLen++; + } + + return outLen; + } + + /** + * Decodes the Hex encoded String data writing it to the given output stream, + * whitespace characters will be ignored. + * + * @param data The data to be encoded + * @param out The {@link OutputStream} instance + * @return the number of bytes produced. + * @throws IOException If encoding failed. + */ + public static int decode(final String data, final OutputStream out) throws IOException { + byte b1, b2; + int length = 0; + + int end = data.length(); + + while (end > 0) { + if (!ignore(data.charAt(end - 1))) { + break; + } + + end--; + } + + int i = 0; + while (i < end) { + while (i < end && ignore(data.charAt(i))) { + i++; + } + + b1 = DECODING_TABLE[data.charAt(i++)]; + + while (i < end && ignore(data.charAt(i))) { + i++; + } + + b2 = DECODING_TABLE[data.charAt(i++)]; + + out.write((b1 << 4) | b2); + + length++; + } + + return length; + } + + /** + * Encodes the input data producing a Hex output stream. + * @param data Input data to encode. + * @return the number of bytes produced. + */ + public static String encode(final byte[] data) { + try { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + encode(data, 0, data.length, out); + out.close(); + return new String(out.toByteArray()); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e.getMessage(), e); + } + } + + /** + * Decodes the HEX input data producing a output stream. + * @param data Input data to be decoded. + * @return A byte array representing the decoded input data. + */ + public static byte[] decode(final String data) { + try { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + decode(data, out); + out.close(); + return out.toByteArray(); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e.getMessage(), e); + } + } + +} Index: codegen/src/main/java/org/exolab/castor/builder/descriptors/DescriptorSourceFactory.java =================================================================== --- codegen/src/main/java/org/exolab/castor/builder/descriptors/DescriptorSourceFactory.java (Revision 6910) +++ codegen/src/main/java/org/exolab/castor/builder/descriptors/DescriptorSourceFactory.java (Arbeitskopie) @@ -431,6 +431,8 @@ addSpecialHandlerLogic(member, xsType, jsc); } + // Add the schema type as defined in the schema + jsc.add("desc.setSchemaType(\"" + xsType.getName() + "\");"); jsc.add("desc.setHandler(handler);"); //-- container