[JENKINS-45892] Merged #2957: prevent model objects from being serialized except at top level.

This commit is contained in:
Jesse Glick committed 2017-08-08 08:48:37 -04:00
commit 14028ec75b
8 files changed
+265 -3

No files matched your search

+33 -1
View File
@@ -50,8 +50,13 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Serializable;
import java.io.Writer;
import java.io.StringWriter;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.io.IOUtils;
@@ -114,6 +119,7 @@ import org.apache.commons.io.IOUtils;
public final class XmlFile {
private final XStream xs;
private final File file;
private static final Map<Object, Void> beingWritten = Collections.synchronizedMap(new IdentityHashMap<>());
public XmlFile(File file) {
this(DEFAULT_XSTREAM,file);
@@ -168,7 +174,12 @@ public final class XmlFile {
AtomicFileWriter w = new AtomicFileWriter(file);
try {
w.write("<?xml version='1.0' encoding='UTF-8'?>\n");
xs.toXML(o,w);
beingWritten.put(o, null);
try {
xs.toXML(o, w);
} finally {
beingWritten.remove(o);
}
w.commit();
} catch(StreamException e) {
throw new IOException(e);
@@ -177,6 +188,27 @@ public final class XmlFile {
}
}
/**
* Provides an XStream replacement for an object unless a call to {@link #write} is currently in progress.
* As per JENKINS-45892 this may be used by any class which expects to be written at top level to an XML file
* but which cannot safely be serialized as a nested object (for example, because it expects some {@code onLoad} hook):
* implement a {@code writeReplace} method delegating to this method.
* The replacement need not be {@link Serializable} since it is only necessary for use from XStream.
* @param o an object ({@code this} from {@code writeReplace})
* @param replacement a supplier of a safely serializable replacement object with a {@code readResolve} method
* @return {@code o}, if {@link #write} is being called on it, else the replacement
* @since 2.74
*/
public static Object replaceIfNotAtTopLevel(Object o, Supplier<Object> replacement) {
if (beingWritten.containsKey(o)) {
return o;
} else {
// Unfortunately we cannot easily tell which XML file is actually being saved here, at least without implementing a custom Converter.
LOGGER.log(Level.WARNING, "JENKINS-45892: reference to {0} being saved but not at top level", o);
return replacement.get();
}
}
public boolean exists() {
return file.exists();
}