File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\util\RobustReflectionConverter.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24 package hudson.util;
25
26 import com.thoughtworks.xstream.converters.ConversionException;
27 import com.thoughtworks.xstream.converters.Converter;
28 import com.thoughtworks.xstream.converters.MarshallingContext;
29 import com.thoughtworks.xstream.converters.SingleValueConverter;
30 import com.thoughtworks.xstream.converters.UnmarshallingContext;
31 import com.thoughtworks.xstream.converters.reflection.NonExistentFieldException;
32 import com.thoughtworks.xstream.converters.reflection.ObjectAccessException;
33 import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider;
34 import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
35 import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
36 import com.thoughtworks.xstream.converters.reflection.SerializationMethodInvoker;
37 import com.thoughtworks.xstream.core.util.Primitives;
38 import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
39 import com.thoughtworks.xstream.io.HierarchicalStreamReader;
40 import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
41 import com.thoughtworks.xstream.mapper.CannotResolveClassException;
42 import com.thoughtworks.xstream.mapper.Mapper;
43 import hudson.diagnosis.OldDataMonitor;
44 import hudson.model.Saveable;
45 import java.lang.reflect.Field;
46 import java.util.ArrayList;
47 import java.util.Collection;
48 import java.util.HashMap;
49 import java.util.HashSet;
50 import java.util.Iterator;
51 import java.util.LinkedList;
52 import java.util.Map;
53 import java.util.Set;
54 import static java.util.logging.Level.WARNING;
55 import java.util.logging.Logger;
56 import javax.annotation.Nonnull;
57
58 /**
59 * Custom {@link ReflectionConverter} that handle errors more gracefully.
60 *
61 * <ul>
62 * <li>If the field is missing, the value is ignored instead of causing an error.
63 * This makes evolution easy.
64 * <li>If the type found in XML is no longer available, the element is skipped
65 * instead of causing an error.
66 * </ul>
67 *
68 */
69 public class RobustReflectionConverter implements Converter {
70
71 protected final ReflectionProvider reflectionProvider;
72 protected final Mapper mapper;
73 protected transient SerializationMethodInvoker serializationMethodInvoker;
74 private transient ReflectionProvider pureJavaReflectionProvider;
75 private final @Nonnull XStream2.ClassOwnership classOwnership;
76
77 public RobustReflectionConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
78 this(mapper, reflectionProvider, new XStream2().new PluginClassOwnership());
79 }
80 RobustReflectionConverter(Mapper mapper, ReflectionProvider reflectionProvider, XStream2.ClassOwnership classOwnership) {
81 this.mapper = mapper;
82 this.reflectionProvider = reflectionProvider;
83 assert classOwnership != null;
84 this.classOwnership = classOwnership;
85 serializationMethodInvoker = new SerializationMethodInvoker();
86 }
87
88 public boolean canConvert(Class type) {
89 return true;
90 }
91
92 public void marshal(Object original, final HierarchicalStreamWriter writer, final MarshallingContext context) {
93 final Object source = serializationMethodInvoker.callWriteReplace(original);
94
95 if (source.getClass() != original.getClass()) {
96 writer.addAttribute(mapper.aliasForAttribute("resolves-to"), mapper.serializedClass(source.getClass()));
97 }
98
99 OwnerContext oc = OwnerContext.find(context);
100 oc.startVisiting(writer, classOwnership.ownerOf(original.getClass()));
101 try {
102 doMarshal(source, writer, context);
103 } finally {
104 oc.stopVisiting();
105 }
106 }
107
108 /** Marks {@code plugin="..."} on elements where the owner is known and distinct from the closest owned ancestor. */
109 private static class OwnerContext extends LinkedList<String> {
110 static OwnerContext find(MarshallingContext context) {
111 OwnerContext c = (OwnerContext) context.get(OwnerContext.class);
112 if (c == null) {
113 c = new OwnerContext();
114 context.put(OwnerContext.class, c);
115 }
116 return c;
117 }
118 private void startVisiting(HierarchicalStreamWriter writer, String owner) {
119 if (owner != null) {
120 boolean redundant = false;
121 for (String parentOwner : this) {
122 if (parentOwner != null) {
123 redundant = parentOwner.equals(owner);
124 break;
125 }
126 }
127 if (!redundant) {
128 writer.addAttribute("plugin", owner);
129 }
130 }
131 addFirst(owner);
132 }
133 private void stopVisiting() {
134 removeFirst();
135 }
136 }
137
138 protected void doMarshal(final Object source, final HierarchicalStreamWriter writer, final MarshallingContext context) {
139 final Set seenFields = new HashSet();
140 final Set seenAsAttributes = new HashSet();
141
142 // Attributes might be preferred to child elements ...
143 reflectionProvider.visitSerializableFields(source, new ReflectionProvider.Visitor() {
144 public void visit(String fieldName, Class type, Class definedIn, Object value) {
145 SingleValueConverter converter = mapper.getConverterFromItemType(fieldName, type, definedIn);
146 if (converter == null) converter = mapper.getConverterFromItemType(fieldName, type);
147 if (converter == null) converter = mapper.getConverterFromItemType(type);
148 if (converter != null) {
149 if (value != null) {
150 final String str = converter.toString(value);
151 if (str != null) {
152 writer.addAttribute(mapper.aliasForAttribute(fieldName), str);
153 }
154 }
155 seenAsAttributes.add(fieldName);
156 }
157 }
158 });
159
160 // Child elements not covered already processed as attributes ...
161 reflectionProvider.visitSerializableFields(source, new ReflectionProvider.Visitor() {
162 public void visit(String fieldName, Class fieldType, Class definedIn, Object newObj) {
163 if (!seenAsAttributes.contains(fieldName) && newObj != null) {
164 Mapper.ImplicitCollectionMapping mapping = mapper.getImplicitCollectionDefForFieldName(source.getClass(), fieldName);
165 if (mapping != null) {
166 if (mapping.getItemFieldName() != null) {
167 Collection list = (Collection) newObj;
168 for (Iterator iter = list.iterator(); iter.hasNext();) {
169 Object obj = iter.next();
170 writeField(fieldName, mapping.getItemFieldName(), mapping.getItemType(), definedIn, obj);
171 }
172 } else {
173 context.convertAnother(newObj);
174 }
175 } else {
176 writeField(fieldName, fieldName, fieldType, definedIn, newObj);
177 seenFields.add(fieldName);
178 }
179 }
180 }
181
182 private void writeField(String fieldName, String aliasName, Class fieldType, Class definedIn, Object newObj) {
183 try {
184 if (!mapper.shouldSerializeMember(definedIn, aliasName)) {
185 return;
186 }
187 ExtendedHierarchicalStreamWriterHelper.startNode(writer, mapper.serializedMember(definedIn, aliasName), fieldType);
188
189 Class actualType = newObj.getClass();
190
191 Class defaultType = mapper.defaultImplementationOf(fieldType);
192 if (!actualType.equals(defaultType)) {
193 String serializedClassName = mapper.serializedClass(actualType);
194 if (!serializedClassName.equals(mapper.serializedClass(defaultType))) {
195 writer.addAttribute(mapper.aliasForSystemAttribute("class"), serializedClassName);
196 }
197 }
198
199 if (seenFields.contains(aliasName)) {
200 writer.addAttribute(mapper.aliasForAttribute("defined-in"), mapper.serializedClass(definedIn));
201 }
202
203 Field field = reflectionProvider.getField(definedIn,fieldName);
204 marshallField(context, newObj, field);
205 writer.endNode();
206 } catch (RuntimeException e) {
207 // intercept an exception so that the stack trace shows how we end up marshalling the object in question
208 throw new RuntimeException("Failed to serialize "+definedIn.getName()+"#"+fieldName+" for "+source.getClass(),e);
209 }
210 }
211
212 });
213 }
214
215 protected void marshallField(final MarshallingContext context, Object newObj, Field field) {
216 Converter converter = mapper.getLocalConverter(field.getDeclaringClass(), field.getName());
217 context.convertAnother(newObj, converter);
218 }
219
220 public Object unmarshal(final HierarchicalStreamReader reader, final UnmarshallingContext context) {
221 Object result = instantiateNewInstance(reader, context);
222 result = doUnmarshal(result, reader, context);
223 return serializationMethodInvoker.callReadResolve(result);
224 }
225
226 public Object doUnmarshal(final Object result, final HierarchicalStreamReader reader, final UnmarshallingContext context) {
227 final SeenFields seenFields = new SeenFields();
228 Iterator it = reader.getAttributeNames();
229 // Remember outermost Saveable encountered, for reporting below
230 if (result instanceof Saveable && context.get("Saveable") == null)
231 context.put("Saveable", result);
232
233 // Process attributes before recursing into child elements.
234 while (it.hasNext()) {
235 String attrAlias = (String) it.next();
236 String attrName = mapper.attributeForAlias(attrAlias);
237 Class classDefiningField = determineWhichClassDefinesField(reader);
238 boolean fieldExistsInClass = fieldDefinedInClass(result, attrName);
239 if (fieldExistsInClass) {
240 Field field = reflectionProvider.getField(result.getClass(), attrName);
241 SingleValueConverter converter = mapper.getConverterFromAttribute(field.getDeclaringClass(),attrName,field.getType());
242 Class type = field.getType();
243 if (converter == null) {
244 converter = mapper.getConverterFromItemType(type);
245 }
246 if (converter != null) {
247 Object value = converter.fromString(reader.getAttribute(attrAlias));
248 if (type.isPrimitive()) {
249 type = Primitives.box(type);
250 }
251 if (value != null && !type.isAssignableFrom(value.getClass())) {
252 throw new ConversionException("Cannot convert type " + value.getClass().getName() + " to type " + type.getName());
253 }
254 reflectionProvider.writeField(result, attrName, value, classDefiningField);
255 seenFields.add(classDefiningField, attrName);
256 }
257 }
258 }
259
260 Map implicitCollectionsForCurrentObject = null;
261 while (reader.hasMoreChildren()) {
262 reader.moveDown();
263
264 try {
265 String fieldName = mapper.realMember(result.getClass(), reader.getNodeName());
266 boolean implicitCollectionHasSameName = mapper.getImplicitCollectionDefForFieldName(result.getClass(), reader.getNodeName()) != null;
267
268 Class classDefiningField = determineWhichClassDefinesField(reader);
269 boolean fieldExistsInClass = !implicitCollectionHasSameName && fieldDefinedInClass(result,fieldName);
270
271 Class type = determineType(reader, fieldExistsInClass, result, fieldName, classDefiningField);
272 final Object value;
273 if (fieldExistsInClass) {
274 Field field = reflectionProvider.getField(result.getClass(),fieldName);
275 value = unmarshalField(context, result, type, field);
276 // TODO the reflection provider should have returned the proper field in first place ....
277 Class definedType = reflectionProvider.getFieldType(result, fieldName, classDefiningField);
278 if (!definedType.isPrimitive()) {
279 type = definedType;
280 }
281 } else {
282 value = context.convertAnother(result, type);
283 }
284
285 if (value != null && !type.isAssignableFrom(value.getClass())) {
286 LOGGER.warning("Cannot convert type " + value.getClass().getName() + " to type " + type.getName());
287 // behave as if we didn't see this element
288 } else {
289 if (fieldExistsInClass) {
290 reflectionProvider.writeField(result, fieldName, value, classDefiningField);
291 seenFields.add(classDefiningField, fieldName);
292 } else {
293 implicitCollectionsForCurrentObject = writeValueToImplicitCollection(context, value, implicitCollectionsForCurrentObject, result, fieldName);
294 }
295 }
296 } catch (NonExistentFieldException e) {
297 LOGGER.log(WARNING,"Skipping a non-existent field "+e.getFieldName(),e);
298 addErrorInContext(context, e);
299 } catch (CannotResolveClassException e) {
300 LOGGER.log(WARNING,"Skipping a non-existent type",e);
301 addErrorInContext(context, e);
302 } catch (LinkageError e) {
303 LOGGER.log(WARNING,"Failed to resolve a type",e);
304 addErrorInContext(context, e);
305 }
306
307 reader.moveUp();
308 }
309
310 // Report any class/field errors in Saveable objects
311 if (context.get("ReadError") != null && context.get("Saveable") == result) {
312 OldDataMonitor.report((Saveable)result, (ArrayList<Throwable>)context.get("ReadError"));
313 context.put("ReadError", null);
314 }
315 return result;
316 }
317
318 public static void addErrorInContext(UnmarshallingContext context, Throwable e) {
319 ArrayList<Throwable> list = (ArrayList<Throwable>)context.get("ReadError");
320 if (list == null)
321 context.put("ReadError", list = new ArrayList<Throwable>());
322 list.add(e);
323 }
324
325 private boolean fieldDefinedInClass(Object result, String attrName) {
326 // during unmarshalling, unmarshal into transient fields like XStream 1.1.3
327 //boolean fieldExistsInClass = reflectionProvider.fieldDefinedInClass(attrName, result.getClass());
328 return reflectionProvider.getFieldOrNull(result.getClass(),attrName)!=null;
329 }
330
331 protected Object unmarshalField(final UnmarshallingContext context, final Object result, Class type, Field field) {
332 Converter converter = mapper.getLocalConverter(field.getDeclaringClass(), field.getName());
333 return context.convertAnother(result, type, converter);
334 }
335
336 private Map writeValueToImplicitCollection(UnmarshallingContext context, Object value, Map implicitCollections, Object result, String itemFieldName) {
337 String fieldName = mapper.getFieldNameForItemTypeAndName(context.getRequiredType(), value.getClass(), itemFieldName);
338 if (fieldName != null) {
339 if (implicitCollections == null) {
340 implicitCollections = new HashMap(); // lazy instantiation
341 }
342 Collection collection = (Collection) implicitCollections.get(fieldName);
343 if (collection == null) {
344 Class fieldType = mapper.defaultImplementationOf(reflectionProvider.getFieldType(result, fieldName, null));
345 if (!Collection.class.isAssignableFrom(fieldType)) {
346 throw new ObjectAccessException("Field " + fieldName + " of " + result.getClass().getName() +
347 " is configured for an implicit Collection, but field is of type " + fieldType.getName());
348 }
349 if (pureJavaReflectionProvider == null) {
350 pureJavaReflectionProvider = new PureJavaReflectionProvider();
351 }
352 collection = (Collection)pureJavaReflectionProvider.newInstance(fieldType);
353 reflectionProvider.writeField(result, fieldName, collection, null);
354 implicitCollections.put(fieldName, collection);
355 }
356 collection.add(value);
357 }
358 return implicitCollections;
359 }
360
361 private Class determineWhichClassDefinesField(HierarchicalStreamReader reader) {
362 String definedIn = reader.getAttribute(mapper.aliasForAttribute("defined-in"));
363 return definedIn == null ? null : mapper.realClass(definedIn);
364 }
365
366 protected Object instantiateNewInstance(HierarchicalStreamReader reader, UnmarshallingContext context) {
367 String readResolveValue = reader.getAttribute(mapper.aliasForAttribute("resolves-to"));
368
369 Class type = readResolveValue != null ? mapper.realClass(readResolveValue) : context.getRequiredType();
370
371 Object currentObject = context.currentObject();
372 if (currentObject != null) {
373 if (type.isInstance(currentObject))
374 return currentObject;
375 }
376 return reflectionProvider.newInstance(type);
377 }
378
379 private static class SeenFields {
380
381 private Set seen = new HashSet();
382
383 public void add(Class definedInCls, String fieldName) {
384 String uniqueKey = fieldName;
385 if (definedInCls != null) {
386 uniqueKey += " [" + definedInCls.getName() + "]";
387 }
388 if (seen.contains(uniqueKey)) {
389 throw new DuplicateFieldException(uniqueKey);
390 } else {
391 seen.add(uniqueKey);
392 }
393 }
394
395 }
396
397 private Class determineType(HierarchicalStreamReader reader, boolean validField, Object result, String fieldName, Class definedInCls) {
398 String classAttribute = reader.getAttribute(mapper.aliasForAttribute("class"));
399 Class fieldType = reflectionProvider.getFieldType(result, fieldName, definedInCls);
400 if (classAttribute != null) {
401 Class specifiedType = mapper.realClass(classAttribute);
402 if(fieldType.isAssignableFrom(specifiedType))
403 // make sure that the specified type in XML is compatible with the field type.
404 // this allows the code to evolve in more flexible way.
405 return specifiedType;
406 }
407 if (!validField) {
408 Class itemType = mapper.getItemTypeForItemFieldName(result.getClass(), fieldName);
409 if (itemType != null) {
410 return itemType;
411 } else {
412 return mapper.realClass(reader.getNodeName());
413 }
414 } else {
415 return mapper.defaultImplementationOf(fieldType);
416 }
417 }
418
419 private Object readResolve() {
420 serializationMethodInvoker = new SerializationMethodInvoker();
421 return this;
422 }
423
424 public static class DuplicateFieldException extends ConversionException {
425 public DuplicateFieldException(String msg) {
426 super(msg);
427 add("duplicate-field", msg);
428 }
429 }
430
431 private static final Logger LOGGER = Logger.getLogger(RobustReflectionConverter.class.getName());
432 }
433