| /* |
| * 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.schema; |
| |
| import java.beans.BeanInfo; |
| import java.beans.IntrospectionException; |
| import java.beans.Introspector; |
| import java.beans.PropertyDescriptor; |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.StringReader; |
| import java.io.StringWriter; |
| import java.lang.reflect.Array; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.math.BigDecimal; |
| import java.math.BigInteger; |
| import java.util.Arrays; |
| import java.util.Date; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import javax.activation.DataHandler; |
| import javax.xml.namespace.QName; |
| import javax.xml.stream.XMLStreamReader; |
| import javax.xml.stream.XMLStreamWriter; |
| |
| import org.apache.axiom.attachments.Attachments; |
| import org.apache.axiom.mime.MultipartWriter; |
| import org.apache.axiom.mime.impl.javamail.JavaMailMultipartWriterFactory; |
| import org.apache.axiom.om.OMAbstractFactory; |
| import org.apache.axiom.om.OMElement; |
| import org.apache.axiom.om.OMOutputFormat; |
| import org.apache.axiom.om.OMXMLBuilderFactory; |
| import org.apache.axiom.om.util.StAXUtils; |
| import org.apache.axiom.soap.SOAPEnvelope; |
| import org.apache.axiom.soap.impl.builder.MTOMStAXSOAPModelBuilder; |
| import org.apache.axis2.databinding.ADBBean; |
| import org.apache.axis2.databinding.ADBException; |
| import org.apache.axis2.databinding.types.HexBinary; |
| import org.apache.axis2.databinding.types.Language; |
| import org.apache.axis2.databinding.types.URI; |
| import org.apache.axis2.util.XMLPrettyPrinter; |
| |
| import junit.framework.TestCase; |
| |
| public abstract class AbstractTestCase extends TestCase { |
| // This is the set of property types that can be compared using Object#equals: |
| private static final Set<Class<?>> simpleJavaTypes = new HashSet<Class<?>>(Arrays.asList(new Class<?>[] { |
| String.class, Boolean.class, Boolean.TYPE, Integer.class, Integer.TYPE, |
| BigInteger.class, BigDecimal.class, Date.class, QName.class, |
| URI.class, Language.class, HexBinary.class |
| })); |
| |
| private static boolean isADBBean(Class<?> beanClass) { |
| return ADBBean.class.isAssignableFrom(beanClass) || beanClass.getName().startsWith("helper."); |
| } |
| |
| private static boolean isEnum(Class<?> beanClass) { |
| try { |
| beanClass.getDeclaredField("_table_"); |
| return true; |
| } catch (NoSuchFieldException ex) { |
| return false; |
| } |
| } |
| |
| private static BeanInfo getBeanInfo(Class<?> beanClass) { |
| try { |
| return Introspector.getBeanInfo(beanClass, Object.class); |
| } catch (IntrospectionException ex) { |
| fail("Failed to introspect " + beanClass); |
| return null; // Make compiler happy |
| } |
| } |
| |
| /** |
| * Assert that two ADB beans are equal. This method recursively compares properties |
| * in the bean. It supports comparison of various property types, including arrays |
| * and DataHandler. |
| * |
| * @param expected |
| * @param actual |
| */ |
| public static void assertBeanEquals(Object expected, Object actual) { |
| if (expected == null) { |
| assertNull(actual); |
| return; |
| } |
| Class<?> beanClass = expected.getClass(); |
| assertEquals(beanClass, actual.getClass()); |
| for (PropertyDescriptor desc : getBeanInfo(beanClass).getPropertyDescriptors()) { |
| String propertyName = desc.getName(); |
| // System.out.println("Comparing property " + propertyName); |
| Method readMethod = desc.getReadMethod(); |
| Object expectedValue; |
| Object actualValue; |
| try { |
| expectedValue = readMethod.invoke(expected); |
| actualValue = readMethod.invoke(actual); |
| } catch (Exception ex) { |
| fail("Failed to get property " + propertyName + " from " + beanClass); |
| return; |
| } |
| assertPropertyValueEquals("property " + propertyName + " in bean " + beanClass, expectedValue, actualValue); |
| } |
| } |
| |
| private static void assertPropertyValueEquals(String message, Object expected, Object actual) { |
| if (expected == null) { |
| assertNull(message, actual); |
| } else { |
| assertNotNull(message, actual); |
| Class<?> type = expected.getClass(); |
| if (type.isArray()) { |
| int expectedLength = Array.getLength(expected); |
| int actualLength = Array.getLength(actual); |
| assertEquals("array length for " + message, expectedLength, actualLength); |
| for (int i=0; i<expectedLength; i++) { |
| assertPropertyValueEquals(message, Array.get(expected, i), Array.get(actual, i)); |
| } |
| } else if (simpleJavaTypes.contains(type)) { |
| assertEquals("value for " + message, expected, actual); |
| } else if (DataHandler.class.isAssignableFrom(type)) { |
| assertDataHandlerEquals((DataHandler)expected, (DataHandler)actual); |
| } else if (OMElement.class.isAssignableFrom(type)) { |
| assertTrue(isOMElementsEqual((OMElement)expected, (OMElement)actual)); |
| } else if (isADBBean(type)) { |
| if (isEnum(type)) { |
| assertSame("enum value for " + message, expected, actual); |
| } else { |
| assertBeanEquals(expected, actual); |
| } |
| } else { |
| fail("Don't know how to compare values of type " + type.getName() + " for " + message); |
| } |
| } |
| } |
| |
| protected static boolean isOMElementsEqual(OMElement omElement1,OMElement omElement2){ |
| boolean isEqual = false; |
| if ((omElement1 == null) || (omElement2 == null)){ |
| isEqual = (omElement1 == omElement2); |
| } else { |
| isEqual = omElement1.getLocalName().equals(omElement2.getLocalName()); |
| } |
| return isEqual; |
| } |
| |
| private static int countDataHandlers(Object bean) throws Exception { |
| int count = 0; |
| for (PropertyDescriptor desc : getBeanInfo(bean.getClass()).getPropertyDescriptors()) { |
| Object value = desc.getReadMethod().invoke(bean); |
| if (value != null) { |
| if (value instanceof DataHandler) { |
| count++; |
| } else if (value.getClass().isArray()) { |
| int length = Array.getLength(value); |
| for (int i=0; i<length; i++) { |
| Object item = Array.get(value, i); |
| if (item != null) { |
| if (item instanceof DataHandler) { |
| count++; |
| } else if (isADBBean(item.getClass())) { |
| count += countDataHandlers(item); |
| } |
| } |
| } |
| } else if (isADBBean(value.getClass())) { |
| count += countDataHandlers(value); |
| } |
| } |
| } |
| return count; |
| } |
| |
| private static void assertDataHandlerEquals(DataHandler expected, DataHandler actual) { |
| try { |
| InputStream in1 = expected.getInputStream(); |
| InputStream in2 = actual.getInputStream(); |
| int b; |
| do { |
| b = in1.read(); |
| assertEquals(b, in2.read()); |
| } while (b != -1); |
| } catch (IOException ex) { |
| fail("Failed to read data handler"); |
| } |
| } |
| |
| public static Object toHelperModeBean(ADBBean bean) throws Exception { |
| Class<?> beanClass = bean.getClass(); |
| Object helperModeBean = null; |
| do { |
| Class<?> helperModeBeanClass = Class.forName("helper." + beanClass.getName()); |
| if (helperModeBean == null) { |
| helperModeBean = helperModeBeanClass.newInstance(); |
| } |
| for (Field field : beanClass.getDeclaredFields()) { |
| if (!Modifier.isStatic(field.getModifiers())) { |
| field.setAccessible(true); |
| Object value = field.get(bean); |
| if (value instanceof ADBBean) { |
| // Try to get the _table_ field if this is an enumeration |
| Map<?,?> enumValues; |
| try { |
| Field tableField = value.getClass().getDeclaredField("_table_"); |
| tableField.setAccessible(true); |
| enumValues = (Map<?,?>)tableField.get(null); |
| } catch (NoSuchFieldException ex) { |
| enumValues = null; |
| } |
| if (enumValues == null) { |
| // Not an enumeration => translate is as a bean |
| value = toHelperModeBean((ADBBean)value); |
| } else { |
| Field tableField = Class.forName("helper." + value.getClass().getName()).getDeclaredField("_table_"); |
| tableField.setAccessible(true); |
| Map<?,?> destEnumValues = (Map<?,?>)tableField.get(null); |
| for (Map.Entry<?,?> entry : enumValues.entrySet()) { |
| if (entry.getValue() == value) { |
| value = destEnumValues.get(entry.getKey()); |
| break; |
| } |
| } |
| } |
| } |
| Field destField = helperModeBeanClass.getDeclaredField(field.getName()); |
| destField.setAccessible(true); |
| destField.set(helperModeBean, value); |
| } |
| } |
| beanClass = beanClass.getSuperclass(); |
| } while (!beanClass.equals(Object.class)); |
| return helperModeBean; |
| } |
| |
| /** |
| * Serialize a bean to XML and then deserialize the XML. |
| * |
| * @param bean the bean to serialize |
| * @return the deserialized bean |
| * @throws Exception |
| */ |
| public static ADBBean serializeDeserialize(ADBBean bean) throws Exception { |
| Class<? extends ADBBean> beanClass = bean.getClass(); |
| OMElement omElement = bean.getOMElement(ADBBeanUtil.getQName(beanClass), OMAbstractFactory.getOMFactory()); |
| String omElementString = omElement.toStringWithConsume(); |
| // System.out.println("om string ==> " + omElementString); |
| XMLStreamReader xmlReader = StAXUtils.createXMLStreamReader(new ByteArrayInputStream(omElementString.getBytes())); |
| return ADBBeanUtil.parse(beanClass, xmlReader); |
| } |
| |
| /** |
| * Serialize a bean to XML, then deserialize the XML and compare the resulting bean to |
| * the original. This will actually do the serialization and deserialization several times |
| * using different approaches in order to increase the test coverage. |
| * |
| * @param bean the bean to serialize |
| * @throws Exception |
| */ |
| public static void testSerializeDeserialize(ADBBean bean) throws Exception { |
| testSerializeDeserialize(bean, bean); |
| } |
| |
| public static void testSerializeDeserialize(ADBBean bean, ADBBean expectedResult) throws Exception { |
| testSerializeDeserializeUsingStAX(bean, expectedResult); |
| testSerializeDeserializeUsingOMStAXWrapper(bean, expectedResult); |
| |
| testSerializeDeserializeWrapped(bean, expectedResult); |
| testSerializeDeserializeUsingMTOM(bean, expectedResult, true); |
| testSerializeDeserializeUsingMTOM(bean, expectedResult, false); |
| testSerializeDeserializeUsingMTOMWithoutOptimize(bean, expectedResult); |
| testSerializeDeserializePrettified(bean, expectedResult); |
| |
| try { |
| Class.forName("helper." + bean.getClass().getName()); |
| } catch (ClassNotFoundException ex) { |
| // Code has not been compiled in helper mode; skip the rest of the tests. |
| return; |
| } |
| |
| Object helperModeBean = toHelperModeBean(bean); |
| Object helperModeExpectedResult = toHelperModeBean(expectedResult); |
| |
| testSerializeDeserializeUsingStAX(helperModeBean, helperModeExpectedResult); |
| testSerializeDeserializeUsingOMStAXWrapper(helperModeBean, helperModeExpectedResult); |
| testSerializeDeserializeWrapped(helperModeBean, helperModeExpectedResult); |
| testSerializeDeserializeUsingMTOM(helperModeBean, helperModeExpectedResult, true); |
| testSerializeDeserializeUsingMTOM(helperModeBean, helperModeExpectedResult, false); |
| testSerializeDeserializeUsingMTOMWithoutOptimize(helperModeBean, helperModeExpectedResult); |
| testSerializeDeserializePrettified(helperModeBean, helperModeExpectedResult); |
| } |
| |
| // Deserialization approach 1: use an XMLStreamReader produced by the StAX parser. |
| private static void testSerializeDeserializeUsingStAX(Object bean, Object expectedResult) throws Exception { |
| OMElement omElement = ADBBeanUtil.getOMElement(bean); |
| String omElementString = omElement.toStringWithConsume(); |
| // System.out.println(omElementString); |
| assertBeanEquals(expectedResult, ADBBeanUtil.parse(bean.getClass(), |
| StAXUtils.createXMLStreamReader(new StringReader(omElementString)))); |
| } |
| |
| // Deserialization approach 2: use an Axiom tree with caching. In this case the |
| // XMLStreamReader implementation is OMStAXWrapper and we test interoperability |
| // between ADB and Axiom's OMStAXWrapper. |
| private static void testSerializeDeserializeUsingOMStAXWrapper(Object bean, Object expectedResult) throws Exception { |
| OMElement omElement = ADBBeanUtil.getOMElement(bean); |
| String omElementString = omElement.toStringWithConsume(); |
| OMElement omElement2 = OMXMLBuilderFactory.createOMBuilder( |
| new StringReader(omElementString)).getDocumentElement(); |
| assertBeanEquals(expectedResult, ADBBeanUtil.parse(bean.getClass(), omElement2.getXMLStreamReader())); |
| } |
| |
| // Approach 3: Serialize the bean as the child of an element that declares a default namespace. |
| // If ADB behaves correctly, this should not have any impact. A failure here may be an indication |
| // of an incorrect usage of XMLStreamWriter#writeStartElement(String). |
| private static void testSerializeDeserializeWrapped(Object bean, Object expectedResult) throws Exception { |
| StringWriter sw = new StringWriter(); |
| XMLStreamWriter writer = StAXUtils.createXMLStreamWriter(sw); |
| writer.writeStartElement("", "root", "urn:test"); |
| writer.writeDefaultNamespace("urn:test"); |
| ADBBeanUtil.serialize(bean, writer); |
| writer.writeEndElement(); |
| writer.flush(); |
| OMElement omElement3 = OMXMLBuilderFactory.createOMBuilder(new StringReader(sw.toString())).getDocumentElement(); |
| assertBeanEquals(expectedResult, ADBBeanUtil.parse(bean.getClass(), omElement3.getFirstElement().getXMLStreamReader())); |
| } |
| |
| private static void testSerializeDeserializeUsingMTOM(Object bean, Object expectedResult, boolean cache) throws Exception { |
| SOAPEnvelope envelope = OMAbstractFactory.getSOAP11Factory().getDefaultEnvelope(); |
| envelope.getBody().addChild(ADBBeanUtil.getOMElement(bean)); |
| ByteArrayOutputStream buffer = new ByteArrayOutputStream(); |
| OMOutputFormat format = new OMOutputFormat(); |
| format.setDoOptimize(true); |
| envelope.serialize(buffer, format); |
| // envelope.serialize(System.out, format); |
| String contentType = format.getContentTypeForMTOM("text/xml"); |
| Attachments attachments = new Attachments(new ByteArrayInputStream(buffer.toByteArray()), contentType); |
| assertEquals(countDataHandlers(bean) + 1, attachments.getAllContentIDs().length); |
| MTOMStAXSOAPModelBuilder builder = new MTOMStAXSOAPModelBuilder(StAXUtils.createXMLStreamReader(attachments.getRootPartInputStream()), attachments); |
| OMElement bodyElement = builder.getSOAPEnvelope().getBody().getFirstElement(); |
| assertBeanEquals(expectedResult, ADBBeanUtil.parse(bean.getClass(), cache ? bodyElement.getXMLStreamReader() : bodyElement.getXMLStreamReaderWithoutCaching())); |
| } |
| |
| // This is a bit special: it serializes the message using MTOM, but without using any xop:Include. This checks |
| // that MTOM decoding works properly even if the client uses unoptimized base64. |
| private static void testSerializeDeserializeUsingMTOMWithoutOptimize(Object bean, Object expectedResult) throws Exception { |
| SOAPEnvelope envelope = OMAbstractFactory.getSOAP11Factory().getDefaultEnvelope(); |
| envelope.getBody().addChild(ADBBeanUtil.getOMElement(bean)); |
| ByteArrayOutputStream buffer = new ByteArrayOutputStream(); |
| OMOutputFormat format = new OMOutputFormat(); |
| MultipartWriter mpWriter = JavaMailMultipartWriterFactory.INSTANCE.createMultipartWriter(buffer, format.getMimeBoundary()); |
| OutputStream rootPartWriter = mpWriter.writePart("application/xop+xml; charset=UTF-8; type=\"text/xml\"", "binary", format.getRootContentId()); |
| envelope.serialize(rootPartWriter, format); |
| rootPartWriter.close(); |
| mpWriter.complete(); |
| // System.out.write(buffer.toByteArray()); |
| String contentType = format.getContentTypeForMTOM("text/xml"); |
| Attachments attachments = new Attachments(new ByteArrayInputStream(buffer.toByteArray()), contentType); |
| MTOMStAXSOAPModelBuilder builder = new MTOMStAXSOAPModelBuilder(StAXUtils.createXMLStreamReader(attachments.getRootPartInputStream()), attachments); |
| OMElement bodyElement = builder.getSOAPEnvelope().getBody().getFirstElement(); |
| assertBeanEquals(expectedResult, ADBBeanUtil.parse(bean.getClass(), bodyElement.getXMLStreamReaderWithoutCaching())); |
| } |
| |
| // This is used to check that ADB correctly handles element whitespace |
| private static void testSerializeDeserializePrettified(Object bean, Object expectedResult) throws Exception { |
| OMElement omElement = ADBBeanUtil.getOMElement(bean); |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| XMLPrettyPrinter.prettify(omElement, baos); |
| // System.out.write(baos.toByteArray()); |
| assertBeanEquals(expectedResult, ADBBeanUtil.parse(bean.getClass(), |
| StAXUtils.createXMLStreamReader(new ByteArrayInputStream(baos.toByteArray())))); |
| } |
| |
| /** |
| * Assert that serializing the given bean should result in an {@link ADBException}. |
| * |
| * @param bean the bean to serialize |
| * @throws Exception |
| */ |
| public static void assertSerializationFailure(ADBBean bean) throws Exception { |
| try { |
| OMElement omElement = bean.getOMElement(ADBBeanUtil.getQName(bean.getClass()), OMAbstractFactory.getOMFactory()); |
| omElement.toStringWithConsume(); |
| fail("Expected ADBException"); |
| } catch (ADBException ex) { |
| // OK: expected |
| } |
| } |
| } |