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

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, Alan Harder
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.google.common.collect.ImmutableList;
27 import com.google.common.collect.ImmutableMap;
28 import com.thoughtworks.xstream.XStream;
29 import com.thoughtworks.xstream.mapper.AnnotationMapper;
30 import com.thoughtworks.xstream.mapper.Mapper;
31 import com.thoughtworks.xstream.mapper.MapperWrapper;
32 import com.thoughtworks.xstream.converters.Converter;
33 import com.thoughtworks.xstream.converters.ConverterMatcher;
34 import com.thoughtworks.xstream.converters.DataHolder;
35 import com.thoughtworks.xstream.converters.MarshallingContext;
36 import com.thoughtworks.xstream.converters.SingleValueConverter;
37 import com.thoughtworks.xstream.converters.SingleValueConverterWrapper;
38 import com.thoughtworks.xstream.converters.UnmarshallingContext;
39 import com.thoughtworks.xstream.core.JVM;
40 import com.thoughtworks.xstream.io.HierarchicalStreamDriver;
41 import com.thoughtworks.xstream.io.HierarchicalStreamReader;
42 import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
43 import com.thoughtworks.xstream.mapper.CannotResolveClassException;
44 import edu.umd.cs.findbugs.annotations.SuppressWarnings;
45 import hudson.PluginManager;
46 import hudson.PluginWrapper;
47 import hudson.diagnosis.OldDataMonitor;
48 import hudson.util.xstream.ImmutableSetConverter;
49 import hudson.util.xstream.ImmutableSortedSetConverter;
50 import jenkins.model.Jenkins;
51 import hudson.model.Label;
52 import hudson.model.Result;
53 import hudson.model.Saveable;
54 import hudson.util.xstream.ImmutableListConverter;
55 import hudson.util.xstream.ImmutableMapConverter;
56 import hudson.util.xstream.MapperDelegate;
57
58 import java.lang.reflect.Constructor;
59 import java.lang.reflect.InvocationTargetException;
60 import java.util.Map;
61 import java.util.concurrent.ConcurrentHashMap;
62 import javax.annotation.CheckForNull;
63
64 /**
65 * {@link XStream} enhanced for additional Java5 support and improved robustness.
66 * @author Kohsuke Kawaguchi
67 */
68 public class XStream2 extends XStream {
69 private RobustReflectionConverter reflectionConverter;
70 private final ThreadLocal<Boolean> oldData = new ThreadLocal<Boolean>();
71 private final @CheckForNull ClassOwnership classOwnership;
72 private final Map<String,Class<?>> compatibilityAliases = new ConcurrentHashMap<String, Class<?>>();
73
74 /**
75 * Hook to insert {@link Mapper}s after they are created.
76 */
77 private MapperInjectionPoint mapperInjectionPoint;
78
79 public XStream2() {
80 init();
81 classOwnership = null;
82 }
83
84 public XStream2(HierarchicalStreamDriver hierarchicalStreamDriver) {
85 super(hierarchicalStreamDriver);
86 init();
87 classOwnership = null;
88 }
89
90 XStream2(ClassOwnership classOwnership) {
91 init();
92 this.classOwnership = classOwnership;
93 }
94
95 @Override
96 public Object unmarshal(HierarchicalStreamReader reader, Object root, DataHolder dataHolder) {
97 // init() is too early to do this
98 // defensive because some use of XStream happens before plugins are initialized.
99 Jenkins h = Jenkins.getInstance();
100 if(h!=null && h.pluginManager!=null && h.pluginManager.uberClassLoader!=null) {
101 setClassLoader(h.pluginManager.uberClassLoader);
102 }
103
104 Object o = super.unmarshal(reader,root,dataHolder);
105 if (oldData.get()!=null) {
106 oldData.remove();
107 if (o instanceof Saveable) OldDataMonitor.report((Saveable)o, "1.106");
108 }
109 return o;
110 }
111
112 @Override
113 protected Converter createDefaultConverter() {
114 // replace default reflection converter
115 reflectionConverter = new RobustReflectionConverter(getMapper(),new JVM().bestReflectionProvider(), new PluginClassOwnership());
116 return reflectionConverter;
117 }
118
119 static String trimVersion(String version) {
120 // XXX seems like there should be some trick with VersionNumber to do this
121 return version.replaceFirst(" .+$", "");
122 }
123
124 private void init() {
125 // list up types that should be marshalled out like a value, without referential integrity tracking.
126 addImmutableType(Result.class);
127
128 registerConverter(new RobustCollectionConverter(getMapper(),getReflectionProvider()),10);
129 registerConverter(new ImmutableMapConverter(getMapper(),getReflectionProvider()),10);
130 registerConverter(new ImmutableSortedSetConverter(getMapper(),getReflectionProvider()),10);
131 registerConverter(new ImmutableSetConverter(getMapper(),getReflectionProvider()),10);
132 registerConverter(new ImmutableListConverter(getMapper(),getReflectionProvider()),10);
133 registerConverter(new ConcurrentHashMapConverter(getMapper(),getReflectionProvider()),10);
134 registerConverter(new CopyOnWriteMap.Tree.ConverterImpl(getMapper()),10); // needs to override MapConverter
135 registerConverter(new DescribableList.ConverterImpl(getMapper()),10); // explicitly added to handle subtypes
136 registerConverter(new Label.ConverterImpl(),10);
137
138 // this should come after all the XStream's default simpler converters,
139 // but before reflection-based one kicks in.
140 registerConverter(new AssociatedConverterImpl(this), -10);
141 }
142
143 @Override
144 protected MapperWrapper wrapMapper(MapperWrapper next) {
145 Mapper m = new CompatibilityMapper(new MapperWrapper(next) {
146 @Override
147 public String serializedClass(Class type) {
148 if (type != null && ImmutableMap.class.isAssignableFrom(type))
149 return super.serializedClass(ImmutableMap.class);
150 else if (type != null && ImmutableList.class.isAssignableFrom(type))
151 return super.serializedClass(ImmutableList.class);
152 else
153 return super.serializedClass(type);
154 }
155 });
156 AnnotationMapper a = new AnnotationMapper(m, getConverterRegistry(), getClassLoader(), getReflectionProvider(), getJvm());
157 a.autodetectAnnotations(true);
158
159 mapperInjectionPoint = new MapperInjectionPoint(a);
160
161 return mapperInjectionPoint;
162 }
163
164 public Mapper getMapperInjectionPoint() {
165 return mapperInjectionPoint.getDelegate();
166 }
167
168 /**
169 * This method allows one to insert additional mappers after {@link XStream2} was created,
170 * but because of the way XStream works internally, this needs to be done carefully.
171 * Namely,
172 *
173 * <ol>
174 * <li>You need to {@link #getMapperInjectionPoint()} wrap it, then put that back into {@link #setMapper(Mapper)}.
175 * <li>The whole sequence needs to be synchronized against this object to avoid a concurrency issue.
176 * </ol>
177 */
178 public void setMapper(Mapper m) {
179 mapperInjectionPoint.setDelegate(m);
180 }
181
182 final class MapperInjectionPoint extends MapperDelegate {
183 public MapperInjectionPoint(Mapper wrapped) {
184 super(wrapped);
185 }
186
187 public Mapper getDelegate() {
188 return delegate;
189 }
190
191 public void setDelegate(Mapper m) {
192 delegate = m;
193 }
194 }
195
196 /**
197 * Adds an alias in case class names change.
198 *
199 * Unlike {@link #alias(String, Class)}, which uses the registered alias name for writing XML,
200 * this method registers an alias to be used only for the sake of reading from XML. This makes
201 * this method usable for the situation when class names change.
202 *
203 * @param oldClassName
204 * Fully qualified name of the old class name.
205 * @param newClass
206 * New class that's field-compatible with the given old class name.
207 * @since 1.416
208 */
209 public void addCompatibilityAlias(String oldClassName, Class newClass) {
210 compatibilityAliases.put(oldClassName,newClass);
211 }
212
213 /**
214 * Prior to Hudson 1.106, XStream 1.1.x was used which encoded "$" in class names
215 * as "-" instead of "_-" that is used now. Up through Hudson 1.348 compatibility
216 * for old serialized data was maintained via {@code XStream11XmlFriendlyMapper}.
217 * However, it was found (HUDSON-5768) that this caused fields with "__" to fail
218 * deserialization due to double decoding. Now this class is used for compatibility.
219 */
220 private class CompatibilityMapper extends MapperWrapper {
221 private CompatibilityMapper(Mapper wrapped) {
222 super(wrapped);
223 }
224
225 @Override
226 public Class realClass(String elementName) {
227 Class s = compatibilityAliases.get(elementName);
228 if (s!=null) return s;
229
230 try {
231 return super.realClass(elementName);
232 } catch (CannotResolveClassException e) {
233 // If a "-" is found, retry with mapping this to "$"
234 if (elementName.indexOf('-') >= 0) try {
235 Class c = super.realClass(elementName.replace('-', '$'));
236 oldData.set(Boolean.TRUE);
237 return c;
238 } catch (CannotResolveClassException e2) { }
239 // Throw original exception
240 throw e;
241 }
242 }
243 }
244
245 /**
246 * If a class defines a nested {@code ConverterImpl} subclass, use that as a {@link Converter}.
247 * Its constructor may have XStream/XStream2 and/or Mapper parameters (or no params).
248 */
249 private static final class AssociatedConverterImpl implements Converter {
250 private final XStream xstream;
251 private final ConcurrentHashMap<Class<?>,Converter> cache =
252 new ConcurrentHashMap<Class<?>,Converter>();
253
254 private AssociatedConverterImpl(XStream xstream) {
255 this.xstream = xstream;
256 }
257
258 private Converter findConverter(Class<?> t) {
259 Converter result = cache.get(t);
260 if (result != null)
261 // ConcurrentHashMap does not allow null, so use this object to represent null
262 return result == this ? null : result;
263 try {
264 if(t==null || t.getClassLoader()==null)
265 return null;
266 Class<?> cl = t.getClassLoader().loadClass(t.getName() + "$ConverterImpl");
267 Constructor<?> c = cl.getConstructors()[0];
268
269 Class<?>[] p = c.getParameterTypes();
270 Object[] args = new Object[p.length];
271 for (int i = 0; i < p.length; i++) {
272 if(p[i]==XStream.class || p[i]==XStream2.class)
273 args[i] = xstream;
274 else if(p[i]== Mapper.class)
275 args[i] = xstream.getMapper();
276 else
277 throw new InstantiationError("Unrecognized constructor parameter: "+p[i]);
278
279 }
280 ConverterMatcher cm = (ConverterMatcher)c.newInstance(args);
281 result = cm instanceof SingleValueConverter
282 ? new SingleValueConverterWrapper((SingleValueConverter)cm)
283 : (Converter)cm;
284 cache.put(t, result);
285 return result;
286 } catch (ClassNotFoundException e) {
287 cache.put(t, this); // See above.. this object in cache represents null
288 return null;
289 } catch (IllegalAccessException e) {
290 IllegalAccessError x = new IllegalAccessError();
291 x.initCause(e);
292 throw x;
293 } catch (InstantiationException e) {
294 InstantiationError x = new InstantiationError();
295 x.initCause(e);
296 throw x;
297 } catch (InvocationTargetException e) {
298 InstantiationError x = new InstantiationError();
299 x.initCause(e);
300 throw x;
301 }
302 }
303
304 public boolean canConvert(Class type) {
305 return findConverter(type)!=null;
306 }
307
308 public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
309 findConverter(source.getClass()).marshal(source,writer,context);
310 }
311
312 public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
313 return findConverter(context.getRequiredType()).unmarshal(reader,context);
314 }
315 }
316
317 /**
318 * Create a nested {@code ConverterImpl} subclass that extends this class to run some
319 * callback code just after a type is unmarshalled by RobustReflectionConverter.
320 * Example: <pre> public static class ConverterImpl extends XStream2.PassthruConverter&lt;MyType&gt; {
321 * public ConverterImpl(XStream2 xstream) { super(xstream); }
322 * @Override protected void callback(MyType obj, UnmarshallingContext context) {
323 * ...
324 * </pre>
325 */
326 public static abstract class PassthruConverter<T> implements Converter {
327 private Converter converter;
328
329 public PassthruConverter(XStream2 xstream) {
330 converter = xstream.reflectionConverter;
331 }
332
333 public boolean canConvert(Class type) {
334 // marshal/unmarshal called directly from AssociatedConverterImpl
335 return false;
336 }
337
338 public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
339 converter.marshal(source, writer, context);
340 }
341
342 public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
343 Object obj = converter.unmarshal(reader, context);
344 callback((T)obj, context);
345 return obj;
346 }
347
348 protected abstract void callback(T obj, UnmarshallingContext context);
349 }
350
351 /**
352 * Marks serialized classes as being owned by particular components.
353 */
354 interface ClassOwnership {
355 /**
356 * Looks up the owner of a class, if any.
357 * @param clazz a class which might be from a plugin
358 * @return an identifier such as plugin name, or null
359 */
360 @CheckForNull String ownerOf(Class<?> clazz);
361 }
362
363 class PluginClassOwnership implements ClassOwnership {
364
365 private PluginManager pm;
366
367 @SuppressWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE") // classOwnership checked for null so why does FB complain?
368 @Override public String ownerOf(Class<?> clazz) {
369 if (classOwnership != null) {
370 return classOwnership.ownerOf(clazz);
371 }
372 if (pm == null) {
373 Jenkins j = Jenkins.getInstance();
374 if (j != null) {
375 pm = j.getPluginManager();
376 }
377 }
378 if (pm == null) {
379 return null;
380 }
381 // TODO: possibly recursively scan super class to discover dependencies
382 PluginWrapper p = pm.whichPlugin(clazz);
383 return p != null ? p.getShortName() + '@' + trimVersion(p.getVersion()) : null;
384 }
385
386 }
387
388 }
389