Child pages
  • Hint on retaining backward compatibility

Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Add table of contents

Table of Contents

Scenario: Adding a new field

...

If you want to fill in your field with a non-trivial default value, you can write the readResolve method, which gets invoked right after your object is resurrected from persistence. readResolve is called by XStream, but is not part of a Jenkins class hierachy, so there is no override. Just put it right in your class alongside your fields, for example in the build pipeline plugin:

No Format
     protected Object readResolve() {
        if (gridBuilder == null) {
            if (selectedJob != null) {
                gridBuilder = new DownstreamProjectGridBuilder(selectedJob);
            }
        }
        return this;
    }

...

Renaming a field is a combination of the above: mark your old field as transient, declare your new field, and then migrate the data from the old format to the new in your readResolve() method:

No Format
     protected transient Long myObjectId;
    protected List<Long> myObjectIds;


    protected Object readResolve() {
        if (myObjectId != null) {
           myObjectIds = Arrays.asList(myObjectId)
        }
        return this;
    }

...

  • You decide to extend a class and create new choosable classes, e.g. more browsers for a SCM-plugin.
  • The old data structure looked like this when you had only one class SCMBrowser:

    No Format
         <browser>
          <url>http://yahoo.com/</url>
        </browser>
    

After

  • Now you decide to add a new NewSCMBrowser, all your SCMBrowsers are extending SCMBrowserBase and your XML suddenly looks like this:

    No Format
         <browser class="org.jenkinsci.plugins.foo.NewSCMBrowser">
          <url>http://yahoo.com/</url>
        </browser>
    

    or

    No Format
         <browser class="org.jenkinsci.plugins.foo.SCMBrowser">
          <url>http://yahoo.com/</url>
        </browser>
    
  • With new jobs, no problem. Old jobs however will probably break.
  • In your SCMBrowserBase class add a method readResolve (see XStream FAQ):

    No Format
        // compatibility with earlier plugins
        public Object readResolve() {
            if (this.getClass()!=SCMBrowserBase.class) {
                return this;
            }
            // make sure to return the default SCMBrowser only if we no class is given in config.
            try {
                return new SCMBrowser(url.toExternalForm());
            } catch (MalformedURLException e) {
                throw new RuntimeException(e);
            }
        }
    

...

For example, to alias Foo in the "old" package to the "updated" one, you can use this method call:

Code Block

Items.XSTREAM2.addCompatibilityAlias("org.acme.old.Foo", org.acme.updated.Foo.class);

To ensure your alias is registered early in the Jenkins boot sequence, you can use the Initializer annotation on a static method, e.g. in your DescriptorImpl:

Code Block

@Initializer(before = InitMilestone.PLUGINS_STARTED)
public static void addAliases() {
    Items.XSTREAM2.addCompatibilityAlias("org.acme.old.Foo", Foo.class);
}

...