in core/src/main/java/hudson/util/RobustReflectionConverter.java [273:390]
public Object doUnmarshal(final Object result, final HierarchicalStreamReader reader, final UnmarshallingContext context) {
final SeenFields seenFields = new SeenFields();
Iterator it = reader.getAttributeNames();
// Remember outermost Saveable encountered, for reporting below
if (result instanceof Saveable && context.get("Saveable") == null)
context.put("Saveable", result);
// Process attributes before recursing into child elements.
while (it.hasNext()) {
String attrAlias = (String) it.next();
String attrName = mapper.attributeForAlias(attrAlias);
Class classDefiningField = determineWhichClassDefinesField(reader);
boolean fieldExistsInClass = fieldDefinedInClass(result, attrName);
if (fieldExistsInClass) {
Field field = reflectionProvider.getField(result.getClass(), attrName);
SingleValueConverter converter = mapper.getConverterFromAttribute(field.getDeclaringClass(),attrName,field.getType());
Class type = field.getType();
if (converter == null) {
converter = mapper.getConverterFromItemType(type);
}
if (converter != null) {
Object value = converter.fromString(reader.getAttribute(attrAlias));
if (type.isPrimitive()) {
type = Primitives.box(type);
}
if (value != null && !type.isAssignableFrom(value.getClass())) {
throw new ConversionException("Cannot convert type " + value.getClass().getName() + " to type " + type.getName());
}
reflectionProvider.writeField(result, attrName, value, classDefiningField);
seenFields.add(classDefiningField, attrName);
}
}
}
Map implicitCollectionsForCurrentObject = null;
while (reader.hasMoreChildren()) {
reader.moveDown();
boolean critical = false;
try {
String fieldName = mapper.realMember(result.getClass(), reader.getNodeName());
for (Class<?> concrete = result.getClass(); concrete != null; concrete = concrete.getSuperclass()) {
// Not quite right since a subclass could shadow a field, but probably suffices:
if (hasCriticalField(concrete, fieldName)) {
critical = true;
break;
}
}
boolean implicitCollectionHasSameName = mapper.getImplicitCollectionDefForFieldName(result.getClass(), reader.getNodeName()) != null;
Class classDefiningField = determineWhichClassDefinesField(reader);
boolean fieldExistsInClass = !implicitCollectionHasSameName && fieldDefinedInClass(result,fieldName);
Class type = determineType(reader, fieldExistsInClass, result, fieldName, classDefiningField);
final Object value;
if (fieldExistsInClass) {
Field field = reflectionProvider.getField(result.getClass(),fieldName);
value = unmarshalField(context, result, type, field);
// TODO the reflection provider should have returned the proper field in first place ....
Class definedType = reflectionProvider.getFieldType(result, fieldName, classDefiningField);
if (!definedType.isPrimitive()) {
type = definedType;
}
} else {
value = context.convertAnother(result, type);
}
if (value != null && !type.isAssignableFrom(value.getClass())) {
LOGGER.warning("Cannot convert type " + value.getClass().getName() + " to type " + type.getName());
// behave as if we didn't see this element
} else {
if (fieldExistsInClass) {
reflectionProvider.writeField(result, fieldName, value, classDefiningField);
seenFields.add(classDefiningField, fieldName);
} else {
implicitCollectionsForCurrentObject = writeValueToImplicitCollection(context, value, implicitCollectionsForCurrentObject, result, fieldName);
}
}
} catch (CriticalXStreamException e) {
throw e;
} catch (XStreamException e) {
if (critical) {
throw new CriticalXStreamException(e);
}
addErrorInContext(context, e);
} catch (LinkageError e) {
if (critical) {
throw e;
}
addErrorInContext(context, e);
}
reader.moveUp();
}
// Report any class/field errors in Saveable objects
if (context.get("ReadError") != null && context.get("Saveable") == result) {
// Avoid any error in OldDataMonitor to be catastrophic. See JENKINS-62231 and JENKINS-59582
// The root cause is the OldDataMonitor extension is not ready before a plugin triggers an error, for
// example when trying to load a field that was created by a new version and you downgrade to the previous
// one.
try {
OldDataMonitor.report((Saveable) result, (ArrayList<Throwable>) context.get("ReadError"));
} catch (Throwable t) {
// it should be already reported, but we report with INFO just in case
StringBuilder message = new StringBuilder("There was a problem reporting unmarshalling field errors");
Level level = Level.WARNING;
if (t instanceof IllegalStateException && t.getMessage().contains("Expected 1 instance of " + OldDataMonitor.class.getName())) {
message.append(". Make sure this code is executed after InitMilestone.EXTENSIONS_AUGMENTED stage, for example in Plugin#postInitialize instead of Plugin#start");
level = Level.INFO; // it was reported when getting the singleton for OldDataMonitor
}
// it should be already reported, but we report with INFO just in case
LOGGER.log(level, message.toString(), t);
}
context.put("ReadError", null);
}
return result;
}