diff --git a/core/src/main/java/hudson/util/XStream2.java b/core/src/main/java/hudson/util/XStream2.java index 4e6a9f035d31..d56537d20f5f 100644 --- a/core/src/main/java/hudson/util/XStream2.java +++ b/core/src/main/java/hudson/util/XStream2.java @@ -83,6 +83,7 @@ import java.util.logging.Logger; import jenkins.model.Jenkins; import jenkins.util.SystemProperties; +import jenkins.util.xstream.AtomicBooleanConverter; import jenkins.util.xstream.SafeURLConverter; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; @@ -273,6 +274,7 @@ private void init() { // http://www.openwall.com/lists/oss-security/2017/04/03/4 denyTypes(new Class[] { void.class, Void.class }); + registerConverter(new AtomicBooleanConverter(), 10); registerConverter(new RobustCollectionConverter(getMapper(), getReflectionProvider()), 10); registerConverter(new RobustMapConverter(getMapper()), 10); registerConverter(new ImmutableMapConverter(getMapper(), getReflectionProvider()), 10); diff --git a/core/src/main/java/jenkins/util/xstream/AtomicBooleanConverter.java b/core/src/main/java/jenkins/util/xstream/AtomicBooleanConverter.java new file mode 100644 index 000000000000..ef958d2c11f2 --- /dev/null +++ b/core/src/main/java/jenkins/util/xstream/AtomicBooleanConverter.java @@ -0,0 +1,45 @@ +package jenkins.util.xstream; + +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.SingleValueConverterWrapper; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.converters.basic.BooleanConverter; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; +import java.util.concurrent.atomic.AtomicBoolean; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +/** Converts an {@link AtomicBoolean}. */ +@Restricted(NoExternalUse.class) +public class AtomicBooleanConverter implements Converter { + + @Override + public boolean canConvert(Class type) { + return type != null && AtomicBoolean.class.isAssignableFrom(type); + } + + @Override + public void marshal( + Object source, HierarchicalStreamWriter writer, MarshallingContext context) { + final AtomicBoolean atomicBoolean = (AtomicBoolean) source; + writer.startNode("value"); + context.convertAnother( + atomicBoolean.get(), new SingleValueConverterWrapper(BooleanConverter.BINARY)); + writer.endNode(); + } + + @Override + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + reader.moveDown(); + Object item = + context.convertAnother( + null, + Boolean.class, + new SingleValueConverterWrapper(BooleanConverter.BINARY)); + boolean value = item instanceof Boolean ? ((Boolean) item).booleanValue() : false; + reader.moveUp(); + return new AtomicBoolean(value); + } +} diff --git a/core/src/test/java/jenkins/util/xstream/AtomicBooleanFieldsTest.java b/core/src/test/java/jenkins/util/xstream/AtomicBooleanFieldsTest.java new file mode 100644 index 000000000000..b7b7c7eefb86 --- /dev/null +++ b/core/src/test/java/jenkins/util/xstream/AtomicBooleanFieldsTest.java @@ -0,0 +1,69 @@ +package jenkins.util.xstream; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import com.thoughtworks.xstream.io.binary.BinaryStreamReader; +import com.thoughtworks.xstream.io.binary.BinaryStreamWriter; +import hudson.util.XStream2; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.Test; + +public class AtomicBooleanFieldsTest { + + public static class Musican { + public String name; + public String genre; + public AtomicBoolean alive; + + public Musican(final String name, final String genre, final AtomicBoolean alive) { + this.name = name; + this.genre = genre; + this.alive = alive; + } + } + + @Test + public void testAtomicBooleanFields() { + List jazzIcons = new ArrayList<>(); + jazzIcons.add(new Musican("Miles Davis", "jazz", new AtomicBoolean(false))); + jazzIcons.add(new Musican("Wynton Marsalis", "jazz", new AtomicBoolean(true))); + + XStream2 xstream = new XStream2(); + xstream.alias("musician", Musican.class); + + String xmlString = xstream.toXML(jazzIcons); + assertEquals( + "\n" + + " \n" + + " Miles Davis\n" + + " jazz\n" + + " \n" + + " 0\n" + + " \n" + + " \n" + + " \n" + + " Wynton Marsalis\n" + + " jazz\n" + + " \n" + + " 1\n" + + " \n" + + " \n" + + "", + xmlString); + List obj = (List) xstream.fromXML(xmlString); + assertNotNull(obj); + assertEquals(xstream.toXML(jazzIcons), xstream.toXML(obj)); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + xstream.marshal(jazzIcons, new BinaryStreamWriter(baos)); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + obj = (List) xstream.unmarshal(new BinaryStreamReader(bais)); + assertNotNull(obj); + assertEquals(xstream.toXML(jazzIcons), xstream.toXML(obj)); + } +}