All Downloads are FREE. Search and download functionalities are using the official Maven repository.

hudson.util.XStream2 Maven / Gradle / Ivy

The newest version!
/**
 * *****************************************************************************
 *
 * Copyright (c) 2004-2010 Oracle Corporation.
 *
 * All rights reserved. This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License v1.0 which
 * accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *
 * Kohsuke Kawaguchi, Alan Harder
 *
 *
 ******************************************************************************
 */
package hudson.util;

import com.google.common.collect.ImmutableMap;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.ConverterMatcher;
import com.thoughtworks.xstream.converters.DataHolder;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.SingleValueConverter;
import com.thoughtworks.xstream.converters.SingleValueConverterWrapper;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.core.JVM;
import com.thoughtworks.xstream.io.HierarchicalStreamDriver;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.mapper.CannotResolveClassException;
import com.thoughtworks.xstream.mapper.Mapper;
import com.thoughtworks.xstream.mapper.MapperWrapper;
import hudson.diagnosis.OldDataMonitor;
import hudson.model.Hudson;
import hudson.model.Label;
import hudson.model.Result;
import hudson.model.Saveable;
import hudson.util.xstream.ImmutableMapConverter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.ConcurrentHashMap;

/**
 * {@link XStream} enhanced for additional Java5 support and improved
 * robustness.
 *
 * @author Kohsuke Kawaguchi
 */
public class XStream2 extends XStream {

    private Converter reflectionConverter;
    private ThreadLocal oldData = new ThreadLocal();

    public XStream2() {
        init();
    }

    public XStream2(HierarchicalStreamDriver hierarchicalStreamDriver) {
        super(hierarchicalStreamDriver);
        init();
    }

    @Override
    public Object unmarshal(HierarchicalStreamReader reader, Object root, DataHolder dataHolder) {
        // init() is too early to do this
        // defensive because some use of XStream happens before plugins are initialized.
        Hudson h = Hudson.getInstance();
        if (h != null && h.pluginManager != null && h.pluginManager.uberClassLoader != null) {
            setClassLoader(h.pluginManager.uberClassLoader);
        }

        Object o = super.unmarshal(reader, root, dataHolder);
        if (oldData.get() != null) {
            oldData.remove();
            if (o instanceof Saveable) {
                OldDataMonitor.report((Saveable) o, "1.106");
            }
        }
        return o;
    }

    private void init() {
        // list up types that should be marshalled out like a value, without referencial integrity tracking.
        addImmutableType(Result.class);

        registerConverter(new RobustCollectionConverter(getMapper(), getReflectionProvider()), XStream.PRIORITY_NORMAL);
        registerConverter(new ImmutableMapConverter(getMapper(), getReflectionProvider()), XStream.PRIORITY_NORMAL);
        registerConverter(new ConcurrentHashMapConverter(getMapper(), getReflectionProvider()), XStream.PRIORITY_NORMAL);
        registerConverter(new CopyOnWriteMap.Tree.ConverterImpl(getMapper()), XStream.PRIORITY_NORMAL); // needs to override MapConverter
        registerConverter(new DescribableList.ConverterImpl(getMapper()), XStream.PRIORITY_NORMAL); // explicitly added to handle subtypes
        registerConverter(new Label.ConverterImpl(), XStream.PRIORITY_NORMAL);

        // this should come after all the XStream's default simpler converters,
        // but before reflection-based one kicks in.
        registerConverter(new AssociatedConverterImpl(this), XStream.PRIORITY_LOW);
        reflectionConverter = new RobustReflectionConverter(getMapper(), new JVM().bestReflectionProvider());
        registerConverter(reflectionConverter, XStream.PRIORITY_VERY_LOW);
    }
     
    @Override
    protected MapperWrapper wrapMapper(MapperWrapper next) {
        MapperWrapper m = new CompatibilityMapper(new MapperWrapper(next) {
            @Override
            public String serializedClass(Class type) {
                if (type != null && ImmutableMap.class.isAssignableFrom(type)) {
                    return super.serializedClass(ImmutableMap.class);
                } else {
                    return super.serializedClass(type);
                }
            }
        });
        // XStream already sets it in 1.4.8
//        AnnotationMapper a = new AnnotationMapper(m, getConverterRegistry(), getClassLoader(), getReflectionProvider(), getJvm());
//        a.autodetectAnnotations(true);
        return m;
    }
    /**
     * Prior to Hudson 1.106, XStream 1.1.x was used which encoded "$" in class
     * names as "-" instead of "_-" that is used now. Up through Hudson 1.348
     * compatibility for old serialized data was maintained via
     * {@code XStream11XmlFriendlyMapper}. However, it was found (HUDSON-5768)
     * that this caused fields with "__" to fail deserialization due to double
     * decoding. Now this class is used for compatibility.
     */
    private class CompatibilityMapper extends MapperWrapper {

        private CompatibilityMapper(Mapper wrapped) {
            super(wrapped);
        }

        @Override
        public Class realClass(String elementName) {
            try {
                return super.realClass(elementName);
            } catch (CannotResolveClassException e) {
                // If a "-" is found, retry with mapping this to "$"
                if (elementName.indexOf('-') >= 0) {
                    try {
                        Class c = super.realClass(elementName.replace('-', '$'));
                        oldData.set(Boolean.TRUE);
                        return c;
                    } catch (CannotResolveClassException e2) {
                    }
                }
                // Throw original exception
                throw e;
            }
        }
    }
    /**
     * If a class defines a nested {@code ConverterImpl} subclass, use that as a
     * {@link Converter}. Its constructor may have XStream/XStream2 and/or
     * Mapper parameters (or no params).
     */
    private static final class AssociatedConverterImpl implements Converter {

        private final XStream xstream;
        private final ConcurrentHashMap cache
                = new ConcurrentHashMap();

        private AssociatedConverterImpl(XStream xstream) {
            this.xstream = xstream;
        }

        private Converter findConverter(Class t) {
            Converter result = cache.get(t);
            if (result != null) // ConcurrentHashMap does not allow null, so use this object to represent null
            {
                return result == this ? null : result;
            }
            try {
                if (t == null || t.getClassLoader() == null) {
                    return null;
                }
                Class cl = t.getClassLoader().loadClass(t.getName() + "$ConverterImpl");
                Constructor c = cl.getConstructors()[0];

                Class[] p = c.getParameterTypes();
                Object[] args = new Object[p.length];
                for (int i = 0; i < p.length; i++) {
                    if (p[i] == XStream.class || p[i] == XStream2.class) {
                        args[i] = xstream;
                    } else if (p[i] == Mapper.class) {
                        args[i] = xstream.getMapper();
                    } else {
                        throw new InstantiationError("Unrecognized constructor parameter: " + p[i]);
                    }

                }
                ConverterMatcher cm = (ConverterMatcher) c.newInstance(args);
                result = cm instanceof SingleValueConverter
                        ? new SingleValueConverterWrapper((SingleValueConverter) cm)
                        : (Converter) cm;
                cache.put(t, result);
                return result;
            } catch (ClassNotFoundException e) {
                cache.put(t, this);  // See above.. this object in cache represents null
                return null;
            } catch (IllegalAccessException e) {
                IllegalAccessError x = new IllegalAccessError();
                x.initCause(e);
                throw x;
            } catch (InstantiationException e) {
                InstantiationError x = new InstantiationError();
                x.initCause(e);
                throw x;
            } catch (InvocationTargetException e) {
                InstantiationError x = new InstantiationError();
                x.initCause(e);
                throw x;
            }
        }

        public boolean canConvert(Class type) {
            return findConverter(type) != null;
        }

        public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
            findConverter(source.getClass()).marshal(source, writer, context);
        }

        public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
            return findConverter(context.getRequiredType()).unmarshal(reader, context);
        }
    }

    /**
     * Create a nested {@code ConverterImpl} subclass that extends this class to
     * run some callback code just after a type is unmarshalled by
     * RobustReflectionConverter. Example:
     * 
 public static class ConverterImpl extends XStream2.PassthruConverter<MyType> {
     *   public ConverterImpl(XStream2 xstream) { super(xstream); }
     *
     * @Override protected void callback(MyType obj, UnmarshallingContext
     * context) { ...
     * 
*/ public static abstract class PassthruConverter implements Converter { private Converter converter; public PassthruConverter(XStream2 xstream) { converter = xstream.reflectionConverter; } public boolean canConvert(Class type) { // marshal/unmarshal called directly from AssociatedConverterImpl return false; } public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) { converter.marshal(source, writer, context); } public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { Object obj = converter.unmarshal(reader, context); callback((T) obj, context); return obj; } protected abstract void callback(T obj, UnmarshallingContext context); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy