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

hudson.diagnosis.OldDataMonitor 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:
 *
 *    Alan Harder
 *
 *
 *******************************************************************************/ 

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 [email protected]
 */
@Extension
public class OldDataMonitor extends AdministrativeMonitor {

    private static Logger LOGGER = Logger.getLogger(OldDataMonitor.class.getName());
    private HashMap data = new HashMap();
    private boolean updating = false;

    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 static void remove(Saveable obj, boolean isDelete) {
        OldDataMonitor odm = (OldDataMonitor) Hudson.getInstance().getAdministrativeMonitor("OldData");
        synchronized (odm) {
            if (odm.updating) {
                return; // Skip during doUpgrade or doDiscard
            }
            odm.data.remove(obj);
            if (isDelete && obj instanceof Job) {
                for (Run r : ((Job) obj).getBuilds()) {
                    odm.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) {
            remove(obj, false);
        }
    };
    @Extension
    public static final ItemListener itemDeleteListener = new ItemListener() {
        @Override
        public void onDeleted(Item item) {
            remove(item, true);
        }
    };
    @Extension
    public static final RunListener runDeleteListener = new RunListener() {
        @Override
        public void onDeleted(Run run) {
            remove(run, true);
        }
    };

    /**
     * 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;
        }
        // Do not throw error if Hudson model object was not yet constructed
        // in the instance configuration was unmarshalled during initial setup
        if (Hudson.getInstance() != null) {
            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 = Hudson.getVersion();
        VersionNumber min, max;
        boolean single = true;
        //TODO: review and check whether we can do it private
        public String extra;

        public VersionRange(String version, String extra) {
            min = max = version != null ? new VersionNumber(version) : null;
            this.extra = extra;
        }

        public String getExtra() {
            return 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) {
            return currentVersion != null && min != null && (currentVersion.digit(0) > min.digit(0)
                    || (currentVersion.digit(0) == min.digit(0)
                    && currentVersion.digit(1) - min.digit(1) >= threshold));
        }
    }

    /**
     * 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);
        updating = true;
        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();
            }
        }
        updating = false;
        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 {
        updating = true;
        for (Iterator> it = data.entrySet().iterator(); it.hasNext();) {
            Map.Entry entry = it.next();
            if (entry.getValue().max == null) {
                entry.getKey().save();
                it.remove();
            }
        }
        updating = false;
        return HttpResponses.forwardToPreviousPage();
    }

    public HttpResponse doIndex(StaplerResponse rsp) throws IOException {
        return new HttpRedirect("manage");
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy