From 07f0405f2ac0ef744cd541c99418ce15f7d08834 Mon Sep 17 00:00:00 2001 From: mindless Date: Tue, 2 Mar 2010 21:19:59 +0000 Subject: [PATCH] Merge r27590:28071 and r28073 from branches/old-data-monitor into trunk. --Merged-in:------------------------------------------------------------ r27601 | mindless | 2010-02-16 20:21:50 -0800 (Tue, 16 Feb 2010) | 8 lines Add OldDataMonitor for tracking loaded data that contained old deprecated data, the Saveable objects that can be save()'ed to persist the data in its new format, and the Hudson releases where these structure changes took place. A "Manage Old Data" form explains how Hudson deals with migrating old data (keeping downgrades possible), lists the old data, and allows upgrade of the data files with changes up through a selected Hudson release. Added calls of OldDataMonitor.report() for six old deprecated fields/data changes. --Merged-in:------------------------------------------------------------ r27683 | mindless | 2010-02-19 11:43:40 -0800 (Fri, 19 Feb 2010) | 5 lines Expand OldDataMonitor to also track unreadable data found while loading. These are reported from util.RobustReflectionConverter and RobustCollectionConverter. "Manage Old Data" screen has additional text about unreadable data, and a form to resave those files which will discard that data. --Merged-in:------------------------------------------------------------ r27715 | mindless | 2010-02-20 08:56:51 -0800 (Sat, 20 Feb 2010) | 3 lines Add Saveable/Item/Run listeners to remove entries tracked by OldDataMonitor if they are resaved or deleted. --Merged-in:------------------------------------------------------------ r27970 | mindless | 2010-02-26 09:02:43 -0800 (Fri, 26 Feb 2010) | 6 lines Add OldDataMonitor reporting for deprecated fields in Mailer and HudsonPrivateSecurityRealm. Includes fix in RobustReflectionConverter to report against outermost Saveable object (User object can contain MyViewsProperty which is also Saveable). Also added XStream2.PassthruConverter which is used to report old data during unmarshalling when the containing Saveable is not known. --Merged-in:------------------------------------------------------------ r27982 | mindless | 2010-02-26 11:35:41 -0800 (Fri, 26 Feb 2010) | 3 lines Fix to show username for User objects instead of "fullDisplayName". Also increased threshold for possible compatibility removal from 100 to 150 releases. --Merged-in:------------------------------------------------------------ r27983 | mindless | 2010-02-26 11:43:06 -0800 (Fri, 26 Feb 2010) | 5 lines Add OldDataMonitor reporting for ParametersAction.build, CauseAction.cause and UpstreamCause.upstreamCause. Note: these will make the list of objects on the OldData/manage screen very long for Hudson installs with lots of builds run on Hudson 1.283 to 1.287. --Merged-in:------------------------------------------------------------ r27987 | mindless | 2010-02-26 12:33:59 -0800 (Fri, 26 Feb 2010) | 4 lines Refactored XStream2.PassthruConverter so uses do not need to be registered with the XStream2 instance, but work for all XStream2 (using ConverterImpl subclass found by AssociatedConverterImpl). --Merged-in:------------------------------------------------------------ r27989 | mindless | 2010-02-26 13:38:35 -0800 (Fri, 26 Feb 2010) | 3 lines Add a cache of ConverterImpl instances in AssociatedConverterImpl to avoid repeated work in findConverter every time such an object is marshalled/unmarshalled. --Merged-in:------------------------------------------------------------ r27996 | mindless | 2010-02-26 16:47:19 -0800 (Fri, 26 Feb 2010) | 2 lines Add OldDataMonitor reporting for HealthReport.description --Merged-in:------------------------------------------------------------ r27997 | mindless | 2010-02-26 16:49:28 -0800 (Fri, 26 Feb 2010) | 5 lines Remove readResolve() in 3 classes where a Converter now does that work. In GlobalMatrixAuthorizationStrategy and AuthorizationMatrixProperty also add unreadable-data reporting when unable to parse a permission type, and report when Item.READ permission is added for migration from < 1.301 --Merged-in:------------------------------------------------------------ r28073 | mindless | 2010-02-28 23:18:06 -0800 (Sun, 28 Feb 2010) | 5 lines Report any old XStream 1.1.x serialized (files from Hudson before 1.106) to OldDataMonitor, using a ThreadLocal since the unmarshal context is not available in the mapper. These use "-" to encode "$" in a class name instead of "_-" used now. +-End-of-merged-revisions----------------------------------------------+ git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@28147 71c3de6d-444a-0410-be80-ed276b4c234a --- .../java/hudson/diagnosis/OldDataMonitor.java | 274 ++++++++++++++++++ .../java/hudson/model/AbstractProject.java | 9 +- core/src/main/java/hudson/model/Cause.java | 22 +- .../main/java/hudson/model/CauseAction.java | 26 +- .../main/java/hudson/model/Descriptor.java | 7 + .../java/hudson/model/FingerprintMap.java | 10 +- .../main/java/hudson/model/HealthReport.java | 25 +- .../java/hudson/model/ParametersAction.java | 15 +- core/src/main/java/hudson/model/Project.java | 12 +- core/src/main/java/hudson/model/Result.java | 9 +- core/src/main/java/hudson/model/Slave.java | 14 +- .../security/AuthorizationMatrixProperty.java | 28 +- .../GlobalMatrixAuthorizationStrategy.java | 37 ++- .../security/HudsonPrivateSecurityRealm.java | 19 +- core/src/main/java/hudson/tasks/Mailer.java | 13 +- .../util/RobustCollectionConverter.java | 5 +- .../util/RobustReflectionConverter.java | 25 +- .../main/java/hudson/util/VersionNumber.java | 4 + core/src/main/java/hudson/util/XStream2.java | 68 ++++- .../hudson/diagnosis/Messages.properties | 3 +- .../diagnosis/OldDataMonitor/manage.jelly | 99 +++++++ .../OldDataMonitor/manage.properties | 51 ++++ .../diagnosis/OldDataMonitor/message.jelly | 35 +++ 23 files changed, 703 insertions(+), 107 deletions(-) create mode 100644 core/src/main/java/hudson/diagnosis/OldDataMonitor.java create mode 100644 core/src/main/resources/hudson/diagnosis/OldDataMonitor/manage.jelly create mode 100644 core/src/main/resources/hudson/diagnosis/OldDataMonitor/manage.properties create mode 100644 core/src/main/resources/hudson/diagnosis/OldDataMonitor/message.jelly diff --git a/core/src/main/java/hudson/diagnosis/OldDataMonitor.java b/core/src/main/java/hudson/diagnosis/OldDataMonitor.java new file mode 100644 index 0000000000..f2fc48e0d5 --- /dev/null +++ b/core/src/main/java/hudson/diagnosis/OldDataMonitor.java @@ -0,0 +1,274 @@ +/* + * The MIT License + * + * Copyright (c) 2004-2010, Sun Microsystems, Inc., Alan Harder + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package hudson.diagnosis; + +import hudson.XmlFile; +import hudson.model.AdministrativeMonitor; +import hudson.model.Hudson; +import hudson.Extension; +import hudson.model.Item; +import hudson.model.Job; +import hudson.model.Run; +import hudson.model.Saveable; +import hudson.model.listeners.ItemListener; +import hudson.model.listeners.RunListener; +import hudson.model.listeners.SaveableListener; +import hudson.util.RobustReflectionConverter; +import hudson.util.VersionNumber; +import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerResponse; +import com.thoughtworks.xstream.converters.UnmarshallingContext; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.TreeSet; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.kohsuke.stapler.HttpRedirect; +import org.kohsuke.stapler.HttpResponse; +import org.kohsuke.stapler.HttpResponses; + +/** + * Tracks whether any data structure changes were corrected when loading XML, + * that could be resaved to migrate that data to the new format. + * + * @author Alan.Harder@Sun.Com + */ +@Extension +public class OldDataMonitor extends AdministrativeMonitor { + private static Logger LOGGER = Logger.getLogger(OldDataMonitor.class.getName()); + + private HashMap data = new HashMap(); + + public OldDataMonitor() { + super("OldData"); + } + + @Override + public String getDisplayName() { + return Messages.OldDataMonitor_DisplayName(); + } + + public boolean isActivated() { + return !data.isEmpty(); + } + + public synchronized Map getData() { + return Collections.unmodifiableMap(data); + } + + private synchronized void remove(Saveable obj) { + data.remove(obj); + if (obj instanceof Job) + for (Run r : ((Job)obj).getBuilds()) + data.remove(r); + } + + // Listeners to remove data here if resaved or deleted in regular Hudson usage + + @Extension + public static final SaveableListener changeListener = new SaveableListener() { + @Override + public void onChange(Saveable obj, XmlFile file) { + ((OldDataMonitor)Hudson.getInstance().getAdministrativeMonitor("OldData")).remove(obj); + } + }; + + @Extension + public static final ItemListener itemDeleteListener = new ItemListener() { + @Override + public void onDeleted(Item item) { + ((OldDataMonitor)Hudson.getInstance().getAdministrativeMonitor("OldData")).remove(item); + } + }; + + @Extension + public static final RunListener runDeleteListener = new RunListener(Run.class) { + @Override + public void onDeleted(Run run) { + ((OldDataMonitor)Hudson.getInstance().getAdministrativeMonitor("OldData")).remove(run); + } + }; + + /** + * Inform monitor that some data in a deprecated format has been loaded, + * and converted in-memory to a new structure. + * @param obj Saveable object; calling save() on this object will persist + * the data in its new format to disk. + * @param version Hudson release when the data structure changed. + */ + public static void report(Saveable obj, String version) { + OldDataMonitor odm = (OldDataMonitor)Hudson.getInstance().getAdministrativeMonitor("OldData"); + synchronized (odm) { + try { + VersionRange vr = odm.data.get(obj); + if (vr != null) vr.add(version); + else odm.data.put(obj, new VersionRange(version, null)); + } catch (IllegalArgumentException ex) { + LOGGER.log(Level.WARNING, "Bad parameter given to OldDataMonitor", ex); + } + } + } + + /** + * Inform monitor that some data in a deprecated format has been loaded, during + * XStream unmarshalling when the Saveable containing this object is not available. + * @param context XStream unmarshalling context + * @param version Hudson release when the data structure changed. + */ + public static void report(UnmarshallingContext context, String version) { + RobustReflectionConverter.addErrorInContext(context, new ReportException(version)); + } + + private static class ReportException extends Exception { + private String version; + private ReportException(String version) { + this.version = version; + } + } + + /** + * Inform monitor that some unreadable data was found while loading. + * @param obj Saveable object; calling save() on this object will discard the unreadable data. + * @param errors Exception(s) thrown while loading, regarding the unreadable classes/fields. + */ + public static void report(Saveable obj, Collection errors) { + StringBuilder buf = new StringBuilder(); + int i = 0; + for (Throwable e : errors) { + if (e instanceof ReportException) { + report(obj, ((ReportException)e).version); + } else { + if (++i > 1) buf.append(", "); + buf.append(e.getClass().getSimpleName()).append(": ").append(e.getMessage()); + } + } + if (buf.length() == 0) return; + OldDataMonitor odm = (OldDataMonitor)Hudson.getInstance().getAdministrativeMonitor("OldData"); + synchronized (odm) { + VersionRange vr = odm.data.get(obj); + if (vr != null) vr.extra = buf.toString(); + else odm.data.put(obj, new VersionRange(null, buf.toString())); + } + } + + public static class VersionRange { + private static VersionNumber currentVersion = new VersionNumber(Hudson.VERSION); + + VersionNumber min, max; + boolean single = true; + public String extra; + + public VersionRange(String version, String extra) { + min = max = version != null ? new VersionNumber(version) : null; + this.extra = extra; + } + + public void add(String version) { + VersionNumber ver = new VersionNumber(version); + if (min==null) { min = max = ver; } + else { + if (ver.isOlderThan(min)) { min = ver; single = false; } + if (ver.isNewerThan(max)) { max = ver; single = false; } + } + } + + @Override + public String toString() { + return min==null ? "" : min.toString() + (single ? "" : " - " + max.toString()); + } + + /** + * Does this version range contain a version more than the given number of releases ago? + * @param threshold Number of releases + * @return True if the major version# differs or the minor# differs by >= threshold + */ + public boolean isOld(int threshold) { + if (min!=null && (currentVersion.digit(0) > min.digit(0) + || (currentVersion.digit(0) == min.digit(0) + && currentVersion.digit(1) - min.digit(1) >= threshold))) + return true; + return false; + } + } + + /** + * Sorted list of unique max-versions in the data set. For select list in jelly. + */ + public synchronized Iterator getVersionList() { + TreeSet set = new TreeSet(); + for (VersionRange vr : data.values()) + if (vr.max!=null) set.add(vr.max); + return set.iterator(); + } + + /** + * Depending on whether the user said "yes" or "no", send him to the right place. + */ + public HttpResponse doAct(StaplerRequest req, StaplerResponse rsp) throws IOException { + if(req.hasParameter("no")) { + disable(true); + return HttpResponses.redirectViaContextPath("/manage"); + } else { + return new HttpRedirect("manage"); + } + } + + /** + * Save all or some of the files to persist data in the new forms. + * Remove those items from the data map. + */ + public synchronized HttpResponse doUpgrade(StaplerRequest req, StaplerResponse rsp) throws IOException { + String thruVerParam = req.getParameter("thruVer"); + VersionNumber thruVer = thruVerParam.equals("all") ? null : new VersionNumber(thruVerParam); + for (Iterator> it = data.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = it.next(); + VersionNumber version = entry.getValue().max; + if (version != null && (thruVer == null || !version.isNewerThan(thruVer))) { + entry.getKey().save(); + it.remove(); + } + } + return HttpResponses.forwardToPreviousPage(); + } + + /** + * Save all files containing only unreadable data (no data upgrades), which discards this data. + * Remove those items from the data map. + */ + public synchronized HttpResponse doDiscard(StaplerRequest req, StaplerResponse rsp) throws IOException { + for (Iterator> it = data.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = it.next(); + if (entry.getValue().max == null) { + entry.getKey().save(); + it.remove(); + } + } + return HttpResponses.forwardToPreviousPage(); + } +} diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java index 1bebfb569c..9ca46c1ab7 100644 --- a/core/src/main/java/hudson/model/AbstractProject.java +++ b/core/src/main/java/hudson/model/AbstractProject.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Brian Westrich, Erik Ramfelt, Ertan Deniz, Jean-Baptiste Quenot, Luca Domenico Milanesio, R. Tyler Ballance, Stephen Connolly, Tom Huybrechts, id:cactusman + * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, Brian Westrich, Erik Ramfelt, Ertan Deniz, Jean-Baptiste Quenot, Luca Domenico Milanesio, R. Tyler Ballance, Stephen Connolly, Tom Huybrechts, id:cactusman * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -29,6 +29,7 @@ import hudson.FilePath; import hudson.Launcher; import hudson.Util; import hudson.cli.declarative.CLIMethod; +import hudson.diagnosis.OldDataMonitor; import hudson.slaves.WorkspaceList; import hudson.model.Cause.LegacyCodeCause; import hudson.model.Cause.UserCause; @@ -219,9 +220,13 @@ public abstract class AbstractProject

,R extends A } }); - if(triggers==null) + // boolean! Can't tell if xml file contained false.. + if (enableRemoteTrigger) OldDataMonitor.report(this, "1.77"); + if(triggers==null) { // it didn't exist in < 1.28 triggers = new Vector>(); + OldDataMonitor.report(this, "1.28"); + } for (Trigger t : triggers) t.start(this,false); if(scm==null) diff --git a/core/src/main/java/hudson/model/Cause.java b/core/src/main/java/hudson/model/Cause.java index 53fcfeaa44..3a62ef413a 100644 --- a/core/src/main/java/hudson/model/Cause.java +++ b/core/src/main/java/hudson/model/Cause.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Michael B. Donohue, Seiji Sogabe + * Copyright (c) 2004-2010, Sun Microsystems, Inc., Michael B. Donohue, Seiji Sogabe * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -27,9 +27,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import hudson.diagnosis.OldDataMonitor; import hudson.model.Queue.*; +import hudson.util.XStream2; import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; +import com.thoughtworks.xstream.converters.UnmarshallingContext; /** * Cause object base class. This class hierarchy is used to keep track of why @@ -122,14 +125,17 @@ public abstract class Cause { public String getShortDescription() { return Messages.Cause_UpstreamCause_ShortDescription(upstreamProject, Integer.toString(upstreamBuild)); } - - private Object readResolve() { - if(upstreamCause != null) { - if(upstreamCauses == null) upstreamCauses = new ArrayList(); - upstreamCauses.add(upstreamCause); - upstreamCause=null; + + public static class ConverterImpl extends XStream2.PassthruConverter { + public ConverterImpl(XStream2 xstream) { super(xstream); } + @Override protected void callback(UpstreamCause uc, UnmarshallingContext context) { + if (uc.upstreamCause != null) { + if (uc.upstreamCauses == null) uc.upstreamCauses = new ArrayList(); + uc.upstreamCauses.add(uc.upstreamCause); + uc.upstreamCause = null; + OldDataMonitor.report(context, "1.288"); + } } - return this; } } diff --git a/core/src/main/java/hudson/model/CauseAction.java b/core/src/main/java/hudson/model/CauseAction.java index e3b306e7eb..0baed2c71c 100644 --- a/core/src/main/java/hudson/model/CauseAction.java +++ b/core/src/main/java/hudson/model/CauseAction.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Michael B. Donohue + * Copyright (c) 2004-2010, Sun Microsystems, Inc., Michael B. Donohue * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,10 +23,13 @@ */ package hudson.model; +import hudson.diagnosis.OldDataMonitor; import hudson.model.Queue.Task; import hudson.model.queue.FoldableAction; +import hudson.util.XStream2; import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; +import com.thoughtworks.xstream.converters.UnmarshallingContext; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -42,7 +45,7 @@ public class CauseAction implements FoldableAction { // there can be multiple causes, so this is deprecated private transient Cause cause; - private List causes = new ArrayList(); + private List causes = new ArrayList(); @Exported(visibility=2) public List getCauses() { @@ -101,13 +104,16 @@ public class CauseAction implements FoldableAction { // no CauseAction found, so add a copy of this one item.getActions().add(new CauseAction(this)); } - - private Object readResolve() { - // if we are being read in from an older version - if(cause != null) { - if(causes == null) causes=new ArrayList(); - causes.add(cause); - } - return this; + + public static class ConverterImpl extends XStream2.PassthruConverter { + public ConverterImpl(XStream2 xstream) { super(xstream); } + @Override protected void callback(CauseAction ca, UnmarshallingContext context) { + // if we are being read in from an older version + if (ca.cause != null) { + if (ca.causes == null) ca.causes = new ArrayList(); + ca.causes.add(ca.cause); + OldDataMonitor.report(context, "1.288"); + } + } } } diff --git a/core/src/main/java/hudson/model/Descriptor.java b/core/src/main/java/hudson/model/Descriptor.java index 0be7e1b591..09842d1146 100644 --- a/core/src/main/java/hudson/model/Descriptor.java +++ b/core/src/main/java/hudson/model/Descriptor.java @@ -27,6 +27,7 @@ import hudson.XmlFile; import hudson.BulkChange; import hudson.Util; import static hudson.Util.singleQuote; +import hudson.diagnosis.OldDataMonitor; import hudson.model.listeners.SaveableListener; import hudson.views.ListViewColumn; import net.sf.json.JSONArray; @@ -714,4 +715,10 @@ public abstract class Descriptor> implements Saveable { * Used in {@link #checkMethods} to indicate that there's no check method. */ private static final String NONE = "\u0000"; + + private Object readResolve() { + if (properties!=null) + OldDataMonitor.report(this, "1.62"); + return this; + } } diff --git a/core/src/main/java/hudson/model/FingerprintMap.java b/core/src/main/java/hudson/model/FingerprintMap.java index 38905f783b..873f8e4ecc 100644 --- a/core/src/main/java/hudson/model/FingerprintMap.java +++ b/core/src/main/java/hudson/model/FingerprintMap.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi + * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,6 +24,7 @@ package hudson.model; import hudson.Util; +import hudson.diagnosis.OldDataMonitor; import hudson.util.KeyedDataStorage; import java.io.File; @@ -56,7 +57,7 @@ public final class FingerprintMap extends KeyedDataStorage { /** * Fix deserialization of older data. - * - * @return this. */ - private Object readResolve() { - // If we are being read back in from an older version - if (localizibleDescription == null) { - localizibleDescription = new NonLocalizable(description == null ? "" : description); + public static class ConverterImpl extends XStream2.PassthruConverter { + public ConverterImpl(XStream2 xstream) { super(xstream); } + @Override protected void callback(HealthReport hr, UnmarshallingContext context) { + // If we are being read back in from an older version + if (hr.localizibleDescription == null) { + hr.localizibleDescription = new NonLocalizable(hr.description == null ? "" : hr.description); + OldDataMonitor.report(context, "1.256"); + } } - return this; } /** @@ -339,7 +343,7 @@ public class HealthReport implements Serializable, Comparable { */ @Override public String toString(Locale locale) { - return nonLocalizable; //To change body of overridden methods use File | Settings | File Templates. + return nonLocalizable; } /** @@ -347,8 +351,7 @@ public class HealthReport implements Serializable, Comparable { */ @Override public String toString() { - return nonLocalizable; //To change body of overridden methods use File | Settings | File Templates. + return nonLocalizable; } } - } diff --git a/core/src/main/java/hudson/model/ParametersAction.java b/core/src/main/java/hudson/model/ParametersAction.java index c0c0050640..a139bdbd54 100644 --- a/core/src/main/java/hudson/model/ParametersAction.java +++ b/core/src/main/java/hudson/model/ParametersAction.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Jean-Baptiste Quenot, Seiji Sogabe, Tom Huybrechts + * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, Jean-Baptiste Quenot, Seiji Sogabe, Tom Huybrechts * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -25,6 +25,7 @@ package hudson.model; import hudson.Util; import hudson.EnvVars; +import hudson.diagnosis.OldDataMonitor; import hudson.model.Queue.QueueAction; import hudson.tasks.BuildStep; import hudson.tasks.BuildWrapper; @@ -139,11 +140,17 @@ public class ParametersAction implements Action, Iterable, Queue return !parameters.isEmpty(); } else { // I don't think we need multiple ParametersActions, but let's be defensive - Set parameters = new HashSet(); + Set params = new HashSet(); for (ParametersAction other: others) { - parameters.addAll(other.parameters); + params.addAll(other.parameters); } - return !parameters.equals(new HashSet(this.parameters)); + return !params.equals(new HashSet(this.parameters)); } } + + private Object readResolve() { + if (build != null) + OldDataMonitor.report(build, "1.283"); + return this; + } } diff --git a/core/src/main/java/hudson/model/Project.java b/core/src/main/java/hudson/model/Project.java index c4ae0450cd..b3a6c53e1f 100644 --- a/core/src/main/java/hudson/model/Project.java +++ b/core/src/main/java/hudson/model/Project.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Jorg Heymans, Stephen Connolly, Tom Huybrechts + * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, Jorg Heymans, Stephen Connolly, Tom Huybrechts * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,6 +24,7 @@ package hudson.model; import hudson.Util; +import hudson.diagnosis.OldDataMonitor; import hudson.model.Descriptor.FormException; import hudson.tasks.BuildStep; import hudson.tasks.BuildStepDescriptor; @@ -85,9 +86,11 @@ public abstract class Project

,B extends Build> public void onLoad(ItemGroup parent, String name) throws IOException { super.onLoad(parent, name); - if(buildWrappers==null) + if (buildWrappers==null) { // it didn't exist in < 1.64 buildWrappers = new DescribableList>(this); + OldDataMonitor.report(this, "1.64"); + } builders.setOwner(this); publishers.setOwner(this); buildWrappers.setOwner(this); @@ -217,4 +220,9 @@ public abstract class Project

,B extends Build> */ @Deprecated private transient String slave; + + private Object readResolve() { + if (slave != null) OldDataMonitor.report(this, "1.60"); + return this; + } } diff --git a/core/src/main/java/hudson/model/Result.java b/core/src/main/java/hudson/model/Result.java index 2dd00a56d2..a21cbdb8a2 100644 --- a/core/src/main/java/hudson/model/Result.java +++ b/core/src/main/java/hudson/model/Result.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi + * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -109,13 +109,6 @@ public final class Result implements Serializable, CustomExportedBean { public String toString() { return name; } - - private Object readResolve() { - for (Result r : all) - if (ordinal==r.ordinal) - return r; - return FAILURE; - } public String toExportedObject() { return name; diff --git a/core/src/main/java/hudson/model/Slave.java b/core/src/main/java/hudson/model/Slave.java index 65427e78bf..09b2281a4c 100644 --- a/core/src/main/java/hudson/model/Slave.java +++ b/core/src/main/java/hudson/model/Slave.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Erik Ramfelt, Martin Eigenbrodt, Stephen Connolly, Tom Huybrechts + * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, Erik Ramfelt, Martin Eigenbrodt, Stephen Connolly, Tom Huybrechts * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -27,6 +27,7 @@ import hudson.FilePath; import hudson.Launcher; import hudson.Util; import hudson.Launcher.RemoteLauncher; +import hudson.diagnosis.OldDataMonitor; import hudson.model.Descriptor.FormException; import hudson.remoting.Callable; import hudson.remoting.VirtualChannel; @@ -51,7 +52,6 @@ import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Set; @@ -117,7 +117,7 @@ public abstract class Slave extends Node implements Serializable { */ private String label=""; - private /*almost final*/ DescribableList,NodePropertyDescriptor> nodeProperties = new DescribableList,NodePropertyDescriptor>(Hudson.getInstance()); + private /*almost final*/ DescribableList,NodePropertyDescriptor> nodeProperties = new DescribableList,NodePropertyDescriptor>(Hudson.getInstance()); /** * Lazily computed set of labels from {@link #label}. @@ -156,12 +156,6 @@ public abstract class Slave extends Node implements Serializable { if (name.equals("")) throw new FormException(Messages.Slave_InvalidConfig_NoName(), null); - // this prevents the config from being saved when slaves are offline. - // on a large deployment with a lot of slaves, some slaves are bound to be offline, - // so this check is harmful. - //if (!localFS.exists()) - // throw new FormException("Invalid slave configuration for " + name + ". No such directory exists: " + localFS, null); - // if (remoteFS.equals("")) // throw new FormException(Messages.Slave_InvalidConfig_NoRemoteDir(name), null); @@ -340,6 +334,8 @@ public abstract class Slave extends Node implements Serializable { if(command.length()>0) command += ' '; agentCommand = command+"java -jar ~/bin/slave.jar"; } + if (command!=null || localFS!=null) + OldDataMonitor.report(Hudson.getInstance(), "1.69"); if (launcher == null) { launcher = (agentCommand == null || agentCommand.trim().length() == 0) ? new JNLPLauncher() diff --git a/core/src/main/java/hudson/security/AuthorizationMatrixProperty.java b/core/src/main/java/hudson/security/AuthorizationMatrixProperty.java index ee2b41b8db..89cc85dc0a 100644 --- a/core/src/main/java/hudson/security/AuthorizationMatrixProperty.java +++ b/core/src/main/java/hudson/security/AuthorizationMatrixProperty.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Yahoo! Inc., Peter Hayes, Tom Huybrechts + * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, Yahoo! Inc., Peter Hayes, Tom Huybrechts * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,6 +23,7 @@ */ package hudson.security; +import hudson.diagnosis.OldDataMonitor; import hudson.model.AbstractProject; import hudson.model.Item; import hudson.model.Job; @@ -32,6 +33,7 @@ import hudson.model.Hudson; import hudson.model.Run; import hudson.Extension; import hudson.util.FormValidation; +import hudson.util.RobustReflectionConverter; import java.util.Arrays; import java.util.HashMap; @@ -188,12 +190,6 @@ public class AuthorizationMatrixProperty extends JobProperty> { } } - private Object readResolve() { - GlobalMatrixAuthorizationStrategy.migrateHudson2324(grantedPermissions); - acl = new AclImpl(); - return this; - } - public SidACL getACL() { return acl; } @@ -264,20 +260,22 @@ public class AuthorizationMatrixProperty extends JobProperty> { reader.getValue(); // we used to use this but not any more. reader.moveUp(); } - while (reader.hasMoreChildren()) { - reader.moveDown(); + while (reader.hasMoreChildren()) { + reader.moveDown(); try { as.add(reader.getValue()); } catch (IllegalArgumentException ex) { Logger.getLogger(AuthorizationMatrixProperty.class.getName()) .log(Level.WARNING,"Skipping a non-existent permission",ex); + RobustReflectionConverter.addErrorInContext(context, ex); } - reader.moveUp(); - } + reader.moveUp(); + } - GlobalMatrixAuthorizationStrategy.migrateHudson2324(as.grantedPermissions); + if (GlobalMatrixAuthorizationStrategy.migrateHudson2324(as.grantedPermissions)) + OldDataMonitor.report(context, "1.301"); - return as; - } - } + return as; + } + } } diff --git a/core/src/main/java/hudson/security/GlobalMatrixAuthorizationStrategy.java b/core/src/main/java/hudson/security/GlobalMatrixAuthorizationStrategy.java index 79ecc158e5..8da2bb45e9 100644 --- a/core/src/main/java/hudson/security/GlobalMatrixAuthorizationStrategy.java +++ b/core/src/main/java/hudson/security/GlobalMatrixAuthorizationStrategy.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Yahoo! Inc. + * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, Yahoo! Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -28,12 +28,14 @@ import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; +import hudson.diagnosis.OldDataMonitor; import hudson.model.Descriptor; import hudson.model.Hudson; import hudson.model.Item; import hudson.util.FormValidation; -import hudson.util.VersionNumber; import hudson.util.FormValidation.Kind; +import hudson.util.VersionNumber; +import hudson.util.RobustReflectionConverter; import hudson.Functions; import hudson.Extension; import net.sf.json.JSONObject; @@ -56,7 +58,6 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.io.IOException; -import java.io.Serializable; /** * Role-based authorization via a matrix. @@ -113,31 +114,27 @@ public class GlobalMatrixAuthorizationStrategy extends AuthorizationStrategy { return sids; } - /** - * In earlier version of Hudson we used to use reflection converter, which calls this method. - * This is now unmarshaller via {@link ConverterImpl} - */ - private Object readResolve() { - migrateHudson2324(grantedPermissions); - acl = new AclImpl(); - return this; - } - /** * Due to HUDSON-2324, we want to inject Item.READ permission to everyone who has Hudson.READ, - * to remain backward compatible + * to remain backward compatible. * @param grantedPermissions */ - /*package*/ static void migrateHudson2324(Map> grantedPermissions) { + /*package*/ static boolean migrateHudson2324(Map> grantedPermissions) { + boolean result = false; if(Hudson.getInstance().isUpgradedFromBefore(new VersionNumber("1.300.*"))) { Set f = grantedPermissions.get(Hudson.READ); - if(f!=null) { + if (f!=null) { Set t = grantedPermissions.get(Item.READ); - if(t!=null) t.addAll(f); - else t=new HashSet(f); + if (t!=null) + result = t.addAll(f); + else { + t = new HashSet(f); + result = true; + } grantedPermissions.put(Item.READ,t); } } + return result; } /** @@ -221,11 +218,13 @@ public class GlobalMatrixAuthorizationStrategy extends AuthorizationStrategy { } catch (IllegalArgumentException ex) { Logger.getLogger(GlobalMatrixAuthorizationStrategy.class.getName()) .log(Level.WARNING,"Skipping a non-existent permission",ex); + RobustReflectionConverter.addErrorInContext(context, ex); } reader.moveUp(); } - migrateHudson2324(as.grantedPermissions); + if (migrateHudson2324(as.grantedPermissions)) + OldDataMonitor.report(context, "1.301"); return as; } diff --git a/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java b/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java index 8f181dc9cd..986d9a4aae 100644 --- a/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java +++ b/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, David Calavera, Seiji Sogabe + * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, David Calavera, Seiji Sogabe * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,9 +23,11 @@ */ package hudson.security; +import com.thoughtworks.xstream.converters.UnmarshallingContext; import groovy.lang.Binding; import hudson.Extension; import hudson.Util; +import hudson.diagnosis.OldDataMonitor; import hudson.model.Descriptor; import hudson.model.Hudson; import hudson.model.ManagementLink; @@ -37,6 +39,7 @@ import hudson.tasks.Mailer; import hudson.util.PluginServletFilter; import hudson.util.Protector; import hudson.util.Scrambler; +import hudson.util.XStream2; import hudson.util.spring.BeanBuilder; import net.sf.json.JSONObject; import org.acegisecurity.Authentication; @@ -405,11 +408,15 @@ public class HudsonPrivateSecurityRealm extends SecurityRealm implements ModelOb return user==null; } - private Object readResolve() { - // If we are being read back in from an older version - if (password!=null && passwordHash==null) - passwordHash = PASSWORD_ENCODER.encodePassword(Scrambler.descramble(password),null); - return this; + public static class ConverterImpl extends XStream2.PassthruConverter

{ + public ConverterImpl(XStream2 xstream) { super(xstream); } + @Override protected void callback(Details d, UnmarshallingContext context) { + // Convert to hashed password and report to monitor if we load old data + if (d.password!=null && d.passwordHash==null) { + d.passwordHash = PASSWORD_ENCODER.encodePassword(Scrambler.descramble(d.password),null); + OldDataMonitor.report(context, "1.283"); + } + } } @Extension diff --git a/core/src/main/java/hudson/tasks/Mailer.java b/core/src/main/java/hudson/tasks/Mailer.java index e363e17736..10412a0d25 100644 --- a/core/src/main/java/hudson/tasks/Mailer.java +++ b/core/src/main/java/hudson/tasks/Mailer.java @@ -27,6 +27,7 @@ import hudson.Launcher; import hudson.Functions; import hudson.Extension; import hudson.Util; +import hudson.diagnosis.OldDataMonitor; import static hudson.Util.fixEmptyAndTrim; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; @@ -36,10 +37,12 @@ import hudson.model.UserPropertyDescriptor; import hudson.model.Hudson; import hudson.util.FormValidation; import hudson.util.Secret; +import hudson.util.XStream2; import org.apache.tools.ant.types.selectors.SelectorUtils; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.export.Exported; +import com.thoughtworks.xstream.converters.UnmarshallingContext; import javax.mail.Authenticator; import javax.mail.Message; @@ -86,7 +89,7 @@ public class Mailer extends Notifier { public boolean sendToIndividuals; // TODO: left so that XStream won't get angry. figure out how to set the error handling behavior - // in XStream. + // in XStream. Deprecated since 2005-04-23. private transient String from; private transient String subject; private transient boolean failureOnly; @@ -483,4 +486,12 @@ public class Mailer extends Notifier { * Debug probe point to be activated by the scripting console. */ public static boolean debug = false; + + public static class ConverterImpl extends XStream2.PassthruConverter { + public ConverterImpl(XStream2 xstream) { super(xstream); } + @Override protected void callback(Mailer m, UnmarshallingContext context) { + if (m.from != null || m.subject != null || m.failureOnly) + OldDataMonitor.report(context, "1.10"); + } + } } diff --git a/core/src/main/java/hudson/util/RobustCollectionConverter.java b/core/src/main/java/hudson/util/RobustCollectionConverter.java index 693bc69d99..5d2e145a8c 100644 --- a/core/src/main/java/hudson/util/RobustCollectionConverter.java +++ b/core/src/main/java/hudson/util/RobustCollectionConverter.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi + * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -35,7 +35,6 @@ import com.thoughtworks.xstream.XStream; import java.util.Collection; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; -import java.util.logging.Level; import java.util.logging.Logger; import static java.util.logging.Level.WARNING; @@ -87,8 +86,10 @@ public class RobustCollectionConverter extends CollectionConverter { collection.add(item); } catch (CannotResolveClassException e) { LOGGER.log(WARNING,"Failed to resolve class",e); + RobustReflectionConverter.addErrorInContext(context, e); } catch (LinkageError e) { LOGGER.log(WARNING,"Failed to resolve class",e); + RobustReflectionConverter.addErrorInContext(context, e); } reader.moveUp(); } diff --git a/core/src/main/java/hudson/util/RobustReflectionConverter.java b/core/src/main/java/hudson/util/RobustReflectionConverter.java index 91b45e37b9..dd1d929488 100644 --- a/core/src/main/java/hudson/util/RobustReflectionConverter.java +++ b/core/src/main/java/hudson/util/RobustReflectionConverter.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi + * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -41,7 +41,11 @@ import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper; import com.thoughtworks.xstream.mapper.Mapper; import com.thoughtworks.xstream.mapper.CannotResolveClassException; +import hudson.diagnosis.OldDataMonitor; +import hudson.model.Saveable; + import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.Iterator; import java.util.Map; import java.util.Set; @@ -49,7 +53,6 @@ import java.util.HashSet; import java.util.Collection; import java.util.HashMap; import java.util.logging.Logger; -import java.util.logging.Level; import static java.util.logging.Level.WARNING; @@ -178,6 +181,9 @@ public class RobustReflectionConverter implements Converter { 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()) { @@ -243,18 +249,33 @@ public class RobustReflectionConverter implements Converter { } } catch (NonExistentFieldException e) { LOGGER.log(WARNING,"Skipping a non-existent field "+e.getFieldName(),e); + addErrorInContext(context, e); } catch (CannotResolveClassException e) { LOGGER.log(WARNING,"Skipping a non-existend type",e); + addErrorInContext(context, e); } catch (LinkageError e) { LOGGER.log(WARNING,"Failed to resolve a type",e); + addErrorInContext(context, e); } reader.moveUp(); } + // Report any class/field errors in Saveable objects + if (context.get("ReadError") != null && context.get("Saveable") == result) { + OldDataMonitor.report((Saveable)result, (ArrayList)context.get("ReadError")); + context.put("ReadError", null); + } return result; } + public static void addErrorInContext(UnmarshallingContext context, Throwable e) { + ArrayList list = (ArrayList)context.get("ReadError"); + if (list == null) + context.put("ReadError", list = new ArrayList()); + list.add(e); + } + private boolean fieldDefinedInClass(Object result, String attrName) { // during unmarshalling, unmarshal into transient fields like XStream 1.1.3 //boolean fieldExistsInClass = reflectionProvider.fieldDefinedInClass(attrName, result.getClass()); diff --git a/core/src/main/java/hudson/util/VersionNumber.java b/core/src/main/java/hudson/util/VersionNumber.java index 35c035f628..ce65bd39c3 100644 --- a/core/src/main/java/hudson/util/VersionNumber.java +++ b/core/src/main/java/hudson/util/VersionNumber.java @@ -99,6 +99,10 @@ public class VersionNumber implements Comparable { } } + public int digit(int idx) { + return digits[idx]; + } + @Override public String toString() { StringBuffer buf = new StringBuffer(); diff --git a/core/src/main/java/hudson/util/XStream2.java b/core/src/main/java/hudson/util/XStream2.java index 0c5d9afe08..453c3d8777 100644 --- a/core/src/main/java/hudson/util/XStream2.java +++ b/core/src/main/java/hudson/util/XStream2.java @@ -38,17 +38,23 @@ 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 hudson.diagnosis.OldDataMonitor; import hudson.model.Hudson; import hudson.model.Result; +import hudson.model.Saveable; 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(); } @@ -67,13 +73,19 @@ public class XStream2 extends XStream { setClassLoader(h.pluginManager.uberClassLoader); } - return super.unmarshal(reader,root,dataHolder); + 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; } @Override protected Converter createDefaultConverter() { // replace default reflection converter - return new RobustReflectionConverter(getMapper(),new JVM().bestReflectionProvider()); + reflectionConverter = new RobustReflectionConverter(getMapper(),new JVM().bestReflectionProvider()); + return reflectionConverter; } private void init() { @@ -113,7 +125,9 @@ public class XStream2 extends XStream { } catch (CannotResolveClassException e) { // If a "-" is found, retry with mapping this to "$" if (elementName.indexOf('-') >= 0) try { - return super.realClass(elementName.replace('-', '$')); + Class c = super.realClass(elementName.replace('-', '$')); + oldData.set(Boolean.TRUE); + return c; } catch (CannotResolveClassException e2) { } // Throw original exception throw e; @@ -123,15 +137,22 @@ public class XStream2 extends XStream { /** * 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; @@ -141,7 +162,7 @@ public class XStream2 extends XStream { Class[] p = c.getParameterTypes(); Object[] args = new Object[p.length]; for (int i = 0; i < p.length; i++) { - if(p[i]==XStream.class) + if(p[i]==XStream.class || p[i]==XStream2.class) args[i] = xstream; else if(p[i]== Mapper.class) args[i] = xstream.getMapper(); @@ -150,10 +171,13 @@ public class XStream2 extends XStream { } ConverterMatcher cm = (ConverterMatcher)c.newInstance(args); - return cm instanceof SingleValueConverter + 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(); @@ -182,4 +206,38 @@ public class XStream2 extends XStream { 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); + } } diff --git a/core/src/main/resources/hudson/diagnosis/Messages.properties b/core/src/main/resources/hudson/diagnosis/Messages.properties index 72af5c5d0e..e42a504ac5 100644 --- a/core/src/main/resources/hudson/diagnosis/Messages.properties +++ b/core/src/main/resources/hudson/diagnosis/Messages.properties @@ -1,2 +1,3 @@ MemoryUsageMonitor.USED=Used -MemoryUsageMonitor.TOTAL=Total \ No newline at end of file +MemoryUsageMonitor.TOTAL=Total +OldDataMonitor.DisplayName=Old Data diff --git a/core/src/main/resources/hudson/diagnosis/OldDataMonitor/manage.jelly b/core/src/main/resources/hudson/diagnosis/OldDataMonitor/manage.jelly new file mode 100644 index 0000000000..c74cb7f60f --- /dev/null +++ b/core/src/main/resources/hudson/diagnosis/OldDataMonitor/manage.jelly @@ -0,0 +1,99 @@ + + + + + + +

${%Manage Old Data}

+

${%blurb.1}

+

${%blurb.2}

+ + + + + + + + ${range} + + + + + + + + + + + + + +
${%Type}${%Name}${%Version}
${obj.class.name}${obj.fullName?:obj.fullDisplayName?:obj.displayName?:obj.name}${range}${item.value.extra}
+

${%blurb.3}

+

${%blurb.4}

+ + + +
+ ${%Resave data files with structure changes no newer than Hudson} + + ${%blurb.5} +
+ + +
+ + ${%No old data was found.} + +
+ +
+

${%Unreadable Data}

+

${%blurb.6}

+ + + + + + + + + + + + +
${%Type}${%Name}${%Error}
${obj.class.name}${obj.fullName?:obj.fullDisplayName?:obj.displayName?:obj.name}${item.value.extra}
+
+
+ + +
+
+
+
diff --git a/core/src/main/resources/hudson/diagnosis/OldDataMonitor/manage.properties b/core/src/main/resources/hudson/diagnosis/OldDataMonitor/manage.properties new file mode 100644 index 0000000000..0e82e646fa --- /dev/null +++ b/core/src/main/resources/hudson/diagnosis/OldDataMonitor/manage.properties @@ -0,0 +1,51 @@ +# The MIT License +# +# Copyright (c) 2004-2010, Sun Microsystems, Inc., Alan Harder +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +blurb.1=\ + When there are changes in how data is stored on disk, Hudson uses the following strategy: \ + data is migrated to the new structure when it is loaded, but the file is not resaved in the \ + new format. This allows for downgrading Hudson if needed. However, it can also leave data \ + on disk in the old format indefinitely. The table below lists files containing such data, \ + and the Hudson version(s) where the data structure was changed. +blurb.2=\ + Sometimes errors occur while reading data (if a plugin adds some data and that plugin is \ + later disabled, if migration code is not written for structure changes, or if Hudson is \ + downgraded after it has already written data not readable by the older version). \ + These errors are logged, but the unreadable data is then skipped over, allowing Hudson to \ + startup and function properly. +blurb.3=\ + The form below may be used to resave these files in the current format. Doing so means a \ + downgrade to a Hudson release older than the selected version will not be able to read the \ + data stored in the new format. Note that simply using Hudson to create and configure jobs \ + and run builds can save data that may not be readable by older Hudson releases, even when \ + this form is not used. Also if any unreadable data errors are reported in the right side \ + of the table above, note that this data will be lost when the file is resaved. +blurb.4=\ + Eventually the code supporting these data migrations may be removed. Compatibility will be \ + retained for at least 150 releases since the structure change. Versions older than this are \ + in bold above, and it is recommended to resave these files. +blurb.5=\ + (downgrade as far back as the selected version may still be possible) +blurb.6=\ + It is acceptable to leave unreadable data in these files, as Hudson will safely ignore it. \ + To avoid the log messages at Hudson startup you can permanently delete the unreadable data \ + by resaving these files using the button below. diff --git a/core/src/main/resources/hudson/diagnosis/OldDataMonitor/message.jelly b/core/src/main/resources/hudson/diagnosis/OldDataMonitor/message.jelly new file mode 100644 index 0000000000..84bc14f6f1 --- /dev/null +++ b/core/src/main/resources/hudson/diagnosis/OldDataMonitor/message.jelly @@ -0,0 +1,35 @@ + + + +
+
+
+ + +
+ ${%You have data stored in an older format and/or unreadable data.} +
+
+
-- GitLab