blob: 5710b32f7e9b95005ab3a94a78a8d64855db1316 [file] [log] [blame]
/*
* 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.brooklyn.util.core.xstream;
import com.google.common.base.Objects;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamConverter;
import com.thoughtworks.xstream.converters.basic.BooleanConverter;
import com.thoughtworks.xstream.converters.extended.ToAttributedValueConverter;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;
import net.minidev.json.JSONObject;
import org.apache.brooklyn.test.Asserts;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.collections.MutableSet;
import org.apache.brooklyn.util.core.config.ConfigBag;
import org.apache.brooklyn.util.core.xstream.LambdaPreventionMapper.LambdaPersistenceMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.testng.Assert.assertEquals;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class XmlSerializerTest {
private static final Logger LOG = LoggerFactory.getLogger(XmlSerializerTest.class);
protected XmlSerializer<Object> serializer;
@BeforeMethod(alwaysRun=true)
private void setUp() {
serializer = new XmlSerializer<Object>();
}
@Test
public void testSimple() throws Exception {
assertSerializeAndDeserialize("abc");
assertSerializeAndDeserialize(1);
assertSerializeAndDeserialize(true);
assertSerializeAndDeserialize(new ObjectHolder(null));
assertSerializeAndDeserialize(new StringHolder("abc"));
assertSerializeAndDeserialize(new ObjectHolder("abc"));
assertSerializeAndDeserialize(new ObjectHolder(Arrays.asList("val1", "val2")));
assertSerializeAndDeserialize(new ObjectHolder(123));
assertSerializeAndDeserialize(new IntHolder(123));
assertSerializeAndDeserialize(new IntegerHolder(123));
}
@Test
public void testXmlOutput() throws Exception {
Asserts.assertEquals(serializer.toString(MutableMap.of("a", 1)),
"<MutableMap>\n" +
" <a type=\"int\">1</a>\n" +
"</MutableMap>");
Asserts.assertEquals(serializer.toString(MutableSet.of(1)),
"<MutableSet>\n" +
" <int>1</int>\n" +
"</MutableSet>");
// no nice serializer for this yet
Asserts.assertEquals(serializer.toString(MutableList.of(1)),
"<MutableList>\n" +
" <int>1</int>\n" +
"</MutableList>"
// old (also accepted as input)
// "<MutableList serialization=\"custom\">\n" +
// " <unserializable-parents/>\n" +
// " <list>\n" +
// " <default>\n" +
// " <size>1</size>\n" +
// " </default>\n" +
// " <int>1</int>\n" +
// " <int>1</int>\n" +
// " </list>\n" +
// "</MutableList>"
);
}
@Test
public void testAnnotationsExample() throws Exception {
assertSerializeAndDeserialize(new RendezvousMessage(123, true, "mycontent"));
}
@Test
public void testObjectFieldDeserializingStringWhenNoClassSpecified() throws Exception {
String tag = "org.apache.brooklyn.util.core.xstream.XmlSerializerTest_-ObjectHolder";
String xml = "<"+tag+"><val>myval</val></"+tag+">";
Object result = serializer.fromString(xml);
assertEquals(((ObjectHolder)result).val, "myval");
}
@Test
public void testIntFieldDeserializingStringWhenNoClassSpecified() throws Exception {
String tag = "org.apache.brooklyn.util.core.xstream.XmlSerializerTest_-IntHolder";
String xml = "<"+tag+"><val>123</val></"+tag+">";
Object result = serializer.fromString(xml);
assertEquals(((IntHolder)result).val, 123);
}
@Test
public void testIntegerFieldDeserializingStringWhenNoClassSpecified() throws Exception {
String tag = "org.apache.brooklyn.util.core.xstream.XmlSerializerTest_-IntegerHolder";
String xml = "<"+tag+"><val>123</val></"+tag+">";
Object result = serializer.fromString(xml);
assertEquals(((IntegerHolder)result).val, Integer.valueOf(123));
}
/**
* See https://issues.apache.org/jira/browse/BROOKLYN-305 and http://x-stream.github.io/faq.html#XML_control_char
*/
@Test
public void testIllegalXmlCharacter() throws Exception {
String val = "PREFIX_\u001b\u0000_SUFFIX";
assertEquals(val.charAt(7), 27); // expect that to give us unicode character 27
assertEquals(val.charAt(8), 0); // expect that to give us unicode character 0
assertSerializeAndDeserialize(val);
assertSerializeAndDeserialize(new StringHolder(val));
}
// lambdas
private void serializeExpectingNull(Supplier<String> s) {
String xml = serializer.toString(s);
Object s2 = serializer.fromString(xml);
Asserts.assertStringContainsIgnoreCase(xml, "null");
Asserts.assertNull(s2);
}
private void serializeExpectingFailure(Supplier<String> s) {
serializeExpectingFailure(s, null);
}
private void serializeExpectingFailure(Object s, Consumer<Throwable> ...otherChecks) {
Asserts.assertFailsWith(()->serializer.toString(s),
error -> {
Asserts.expectedFailureContainsIgnoreCase(error, "lambda");
if (otherChecks!=null) Arrays.asList(otherChecks).forEach(c -> c.accept(error));
return true;
});
}
private void serializeExpectingHelloSupplied(Supplier<String> s) {
String xml = serializer.toString(s);
Object s2 = serializer.fromString(xml);
Asserts.assertInstanceOf(s2, Supplier.class);
Asserts.assertEquals( ((Supplier<String>)s2).get(), s.get() );
}
interface SerializableSupplier<T> extends Supplier<T>, Serializable {}
@Test
public void testLambdaXstreamDefaultNotSerializable() throws Exception {
serializeExpectingNull( () -> "hello" );
}
@Test
public void testLambdaXstreamDefaultSerializable() throws Exception {
serializeExpectingHelloSupplied( (SerializableSupplier<String>) () -> "hello" );
}
@Test
public void testLambdaXstreamFailingAllSerializable() throws Exception {
serializer = new XmlSerializer<Object>(null, null, LambdaPreventionMapper.factory(ConfigBag.newInstance().configure(LambdaPreventionMapper.LAMBDA_PERSISTENCE, LambdaPersistenceMode.FAIL)));
serializeExpectingFailure(() -> "hello");
serializeExpectingFailure((SerializableSupplier<String>) () -> "hello");
serializeExpectingFailure(MutableMap.of("key", (Supplier) () -> "hello"), e -> Asserts.expectedFailureContainsIgnoreCase(e, "MutableMap/key"));
}
@Test(groups="WIP")
public void testLambdaXstreamFailingWithNonSerializableException() throws Exception {
SafeThrowableConverter.TODO++;
// this test passes but the fact it comes back as object is problematic; if the field needs a supplier or something else, it won't deserialize.
serializer = new XmlSerializer<Object>(null, null, LambdaPreventionMapper.factory(ConfigBag.newInstance().configure(LambdaPreventionMapper.LAMBDA_PERSISTENCE, LambdaPersistenceMode.FAIL)));
String safelyTidiedException = serializer.toString(MutableMap.of("key", new RuntimeException("some exception",
new TestExceptionWithContext("wrapped exception", null, (Supplier) () -> "hello"))));
Object tidiedException = serializer.fromString(safelyTidiedException);
Throwable e1 = (Throwable) ((Map)tidiedException).get("key");
Throwable e2 = e1.getCause();
Asserts.assertEquals("wrapped exception", e2.getMessage());
Asserts.assertThat(((TestExceptionWithContext)e2).context, x -> x==null || x.getClass().equals(Object.class));
}
public static class TestExceptionWithContext extends Exception {
final Object context;
public TestExceptionWithContext(String msg, Throwable cause, Object context) {
super(msg, cause);
this.context = context;
}
}
@Test
public void testLambdaXstreamFailingNonSerializable() throws Exception {
serializer = new XmlSerializer<Object>(null, null, LambdaPreventionMapper.factory(ConfigBag.newInstance()));
serializeExpectingFailure( () -> "hello" );
serializeExpectingHelloSupplied( (SerializableSupplier<String>) () -> "hello" );
}
protected Object assertSerializeAndDeserialize(Object val) throws Exception {
String xml = serializer.toString(val);
Object result = serializer.fromString(xml);
LOG.debug("val="+val+"'; xml="+xml+"; result="+result);
assertEquals(result, val);
return result;
}
public static class StringHolder {
public String val;
StringHolder(String val) {
this.val = val;
}
@Override
public boolean equals(Object obj) {
return (obj instanceof StringHolder) && val.equals(((StringHolder)obj).val);
}
@Override
public int hashCode() {
return val.hashCode();
}
}
private static class ObjectHolder {
@XStreamConverter(ObjectWithDefaultStringImplConverter.class)
private final Object val;
ObjectHolder(Object val) {
this.val = val;
}
@Override
public boolean equals(Object obj) {
return (obj instanceof ObjectHolder) && Objects.equal(val, ((ObjectHolder)obj).val);
}
@Override
public int hashCode() {
return (val == null) ? 259238 : val.hashCode();
}
@Override
public String toString() {
try {
return "ObjectHolder(val="+val+", type="+(val == null ? null : val.getClass())+")";
} catch (NullPointerException e) {
System.out.println("val="+val);
e.printStackTrace();
return "what?";
}
}
}
public static class IntHolder {
public int val;
public IntHolder(int val) {
this.val = val;
}
@Override
public boolean equals(Object obj) {
return (obj instanceof IntHolder) && val == ((IntHolder)obj).val;
}
@Override
public int hashCode() {
return val;
}
}
public static class IntegerHolder {
public Integer val;
IntegerHolder(int val) {
this.val = val;
}
@Override
public boolean equals(Object obj) {
return (obj instanceof IntegerHolder) && val.equals(((IntegerHolder)obj).val);
}
@Override
public int hashCode() {
return (val == null) ? 259238 : val.hashCode();
}
}
@XStreamAlias("message")
@XStreamConverter(value=ToAttributedValueConverter.class, strings={"content"})
static class RendezvousMessage {
@XStreamAlias("type")
private int messageType;
private String content;
@XStreamConverter(value=BooleanConverter.class, booleans={false}, strings={"yes", "no"})
private boolean important;
public RendezvousMessage(int messageType, boolean important, String content) {
this.messageType = messageType;
this.important = important;
this.content = content;
}
@Override
public int hashCode() {
return messageType;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof RendezvousMessage)) return false;
RendezvousMessage o = (RendezvousMessage) obj;
return (messageType == o.messageType) && important == o.important && content.equals(o.content);
}
}
static class MinidevJsonObjectHolder {
JSONObject jo;
@Override
public boolean equals(Object o2) {
return (o2 instanceof MinidevJsonObjectHolder) && java.util.Objects.equals( ((MinidevJsonObjectHolder)o2).jo, jo );
}
}
@Test
public void testMinidevJsonObject() throws Exception {
JSONObject x = new JSONObject(MutableMap.of("cc", 3));
x = new JSONObject(MutableMap.of("a", 1, "b", 2, "c", x));
Map x0 = (Map) assertSerializeAndDeserialize(x);
Asserts.assertTrue(x0 instanceof JSONObject);
Asserts.assertTrue(x0.get("c") instanceof JSONObject);
// holder test doesn't really do anything here as above preserves type, type info stored in attribute;
// but kept for good measure
MinidevJsonObjectHolder y = new MinidevJsonObjectHolder();
y.jo = x;
MinidevJsonObjectHolder y0 = (MinidevJsonObjectHolder) assertSerializeAndDeserialize(y);
Asserts.assertTrue(y0.jo instanceof JSONObject);
}
}