Changeset 15508 in josm


Ignore:
Timestamp:
2019-11-04T22:19:54+01:00 (4 years ago)
Author:
Don-vip
Message:

fix #18277 - Allow plugins to implement Destroyable if they want to allow restartless updates/removals (patch by taylor.smock)

Location:
trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/gui/preferences/plugin/PluginPreference.java

    r15121 r15508  
    5656import org.openstreetmap.josm.gui.widgets.FilterField;
    5757import org.openstreetmap.josm.plugins.PluginDownloadTask;
     58import org.openstreetmap.josm.plugins.PluginHandler;
    5859import org.openstreetmap.josm.plugins.PluginInformation;
    5960import org.openstreetmap.josm.plugins.ReadLocalPluginInformationTask;
     
    325326            Collections.sort(l);
    326327            Config.getPref().putList("plugins", l);
    327             if (!model.getNewlyDeactivatedPlugins().isEmpty())
    328                 return true;
     328            List<PluginInformation> deactivatedPlugins = model.getNewlyDeactivatedPlugins();
     329            if (!deactivatedPlugins.isEmpty()) {
     330                boolean requiresRestart = PluginHandler.removePlugins(deactivatedPlugins);
     331                if (requiresRestart)
     332                    return requiresRestart;
     333            }
    329334            for (PluginInformation pi : model.getNewlyActivatedPlugins()) {
    330335                if (!pi.canloadatruntime)
  • trunk/src/org/openstreetmap/josm/plugins/PluginHandler.java

    r15505 r15508  
    6969import org.openstreetmap.josm.io.OnlineResource;
    7070import org.openstreetmap.josm.spi.preferences.Config;
     71import org.openstreetmap.josm.tools.Destroyable;
    7172import org.openstreetmap.josm.tools.GBC;
    7273import org.openstreetmap.josm.tools.I18n;
     
    11691170                        tr("Update plugins")
    11701171                );
    1171 
    11721172                try {
    11731173                    pluginDownloadTask.run();
     
    13331333                    pluginsToLoad.stream().filter(x -> x.libraries.contains(oldPluginURL)).forEach(
    13341334                            x -> Collections.replaceAll(x.libraries, oldPluginURL, newPluginURL));
     1335
     1336                    // Attempt to update loaded plugin (must implement Destroyable)
     1337                    PluginInformation tInfo = pluginsToLoad.parallelStream()
     1338                            .filter(x -> x.libraries.contains(newPluginURL)).findAny().orElse(null);
     1339                    if (tInfo != null) {
     1340                        Object tUpdatedPlugin = getPlugin(tInfo.name);
     1341                        if (tUpdatedPlugin instanceof Destroyable) {
     1342                            ((Destroyable) tUpdatedPlugin).destroy();
     1343                            PluginHandler.loadPlugins(getInfoPanel(), Collections.singleton(tInfo),
     1344                                    NullProgressMonitor.INSTANCE);
     1345                        }
     1346                    }
    13351347                } catch (MalformedURLException e) {
    13361348                    Logging.warn(e);
     
    16441656        }
    16451657    }
     1658
     1659    /**
     1660     * Remove deactivated plugins, returning true if JOSM should restart
     1661     *
     1662     * @param deactivatedPlugins The plugins to deactivate
     1663     *
     1664     * @return true if there was a plugin that requires a restart
     1665     * @since 15508
     1666     */
     1667    public static boolean removePlugins(List<PluginInformation> deactivatedPlugins) {
     1668        List<Destroyable> noRestart = deactivatedPlugins.parallelStream()
     1669                .map(info -> PluginHandler.getPlugin(info.name)).filter(Destroyable.class::isInstance)
     1670                .map(Destroyable.class::cast).collect(Collectors.toList());
     1671        boolean restartNeeded;
     1672        try {
     1673            noRestart.forEach(Destroyable::destroy);
     1674            new ArrayList<>(pluginList).stream().filter(proxy -> noRestart.contains(proxy.getPlugin()))
     1675                    .forEach(pluginList::remove);
     1676            restartNeeded = deactivatedPlugins.size() != noRestart.size();
     1677        } catch (Exception e) {
     1678            Logging.error(e);
     1679            restartNeeded = true;
     1680        }
     1681        return restartNeeded;
     1682    }
    16461683}
  • trunk/test/unit/org/openstreetmap/josm/plugins/PluginHandlerTestIT.java

    r15242 r15508  
    88import java.awt.GraphicsEnvironment;
    99import java.awt.HeadlessException;
     10import java.io.IOException;
     11import java.util.ArrayList;
    1012import java.util.Arrays;
    1113import java.util.Collection;
     
    1416import java.util.List;
    1517import java.util.Map;
     18import java.util.Map.Entry;
    1619import java.util.Set;
    1720import java.util.function.Consumer;
     
    1922
    2023import org.apache.commons.lang3.exception.ExceptionUtils;
    21 import org.junit.Rule;
     24import org.junit.BeforeClass;
     25import org.junit.ClassRule;
    2226import org.junit.Test;
     27import org.openstreetmap.josm.TestUtils;
    2328import org.openstreetmap.josm.data.Preferences;
    2429import org.openstreetmap.josm.data.gpx.GpxData;
     
    3136import org.openstreetmap.josm.spi.preferences.Config;
    3237import org.openstreetmap.josm.testutils.JOSMTestRules;
     38import org.openstreetmap.josm.tools.Destroyable;
    3339import org.openstreetmap.josm.tools.Utils;
    3440
     
    4046public class PluginHandlerTestIT {
    4147
     48    private static List<String> errorsToIgnore = new ArrayList<>();
    4249    /**
    4350     * Setup test.
    4451     */
    45     @Rule
     52    @ClassRule
    4653    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
    47     public JOSMTestRules test = new JOSMTestRules().main().projection().preferences().https().timeout(10*60*1000);
     54    public static JOSMTestRules test = new JOSMTestRules().main().projection().preferences().https()
     55            .timeout(10 * 60 * 1000);
     56
     57    /**
     58     * Setup test
     59     *
     60     * @throws IOException in case of I/O error
     61     */
     62    @BeforeClass
     63    public static void beforeClass() throws IOException {
     64        errorsToIgnore.addAll(TestUtils.getIgnoredErrorMessages(PluginHandlerTestIT.class));
     65    }
    4866
    4967    /**
     
    7593        }
    7694
     95        Map<String, Throwable> noRestartExceptions = new HashMap<>();
     96        testCompletelyRestartlessPlugins(loadedPlugins, noRestartExceptions);
     97
    7798        debugPrint(invalidManifestEntries);
    7899        debugPrint(loadingExceptions);
    79100        debugPrint(layerExceptions);
     101        debugPrint(noRestartExceptions);
     102
     103        invalidManifestEntries = filterKnownErrors(invalidManifestEntries);
     104        loadingExceptions = filterKnownErrors(loadingExceptions);
     105        layerExceptions = filterKnownErrors(layerExceptions);
     106        noRestartExceptions = filterKnownErrors(noRestartExceptions);
     107
    80108        String msg = Arrays.toString(invalidManifestEntries.entrySet().toArray()) + '\n' +
    81109                     Arrays.toString(loadingExceptions.entrySet().toArray()) + '\n' +
    82                      Arrays.toString(layerExceptions.entrySet().toArray());
     110                Arrays.toString(layerExceptions.entrySet().toArray()) + '\n'
     111                + Arrays.toString(noRestartExceptions.entrySet().toArray());
    83112        assertTrue(msg, invalidManifestEntries.isEmpty() && loadingExceptions.isEmpty() && layerExceptions.isEmpty());
     113    }
     114
     115    private static void testCompletelyRestartlessPlugins(List<PluginInformation> loadedPlugins,
     116            Map<String, Throwable> noRestartExceptions) {
     117        try {
     118            List<PluginInformation> restartable = loadedPlugins.parallelStream()
     119                    .filter(info -> PluginHandler.getPlugin(info.name) instanceof Destroyable)
     120                    .collect(Collectors.toList());
     121            // ensure good plugin behavior with regards to Destroyable (i.e., they can be
     122            // removed and readded)
     123            for (int i = 0; i < 2; i++) {
     124                assertFalse(PluginHandler.removePlugins(restartable));
     125                assertTrue(restartable.stream().noneMatch(info -> PluginHandler.getPlugins().contains(info)));
     126                loadPlugins(restartable);
     127            }
     128
     129            assertTrue(PluginHandler.removePlugins(loadedPlugins));
     130            assertTrue(restartable.parallelStream().noneMatch(info -> PluginHandler.getPlugins().contains(info)));
     131        } catch (Exception | LinkageError t) {
     132            Throwable root = ExceptionUtils.getRootCause(t);
     133            root.printStackTrace();
     134            noRestartExceptions.put(findFaultyPlugin(loadedPlugins, root), root);
     135        }
     136    }
     137
     138    private static <T> Map<String, T> filterKnownErrors(Map<String, T> errorMap) {
     139        return errorMap.entrySet().parallelStream()
     140                .filter(entry -> !errorsToIgnore.contains(convertEntryToString(entry)))
     141                .collect(Collectors.toMap(Entry::getKey, Entry::getValue));
    84142    }
    85143
     
    87145        System.out.println(invalidManifestEntries.entrySet()
    88146                .stream()
    89                 .map(e -> e.getKey() + "=\"" + e.getValue() + "\"")
     147                .map(e -> convertEntryToString(e))
    90148                .collect(Collectors.joining(", ")));
     149    }
     150
     151    private static String convertEntryToString(Entry<String, ?> entry) {
     152        return entry.getKey() + "=\"" + entry.getValue() + "\"";
    91153    }
    92154
     
    134196        downloadPlugins(plugins);
    135197
     198        loadPlugins(plugins);
     199    }
     200
     201    static void loadPlugins(List<PluginInformation> plugins) {
    136202        // Load early plugins
    137203        PluginHandler.loadEarlyPlugins(null, plugins, null);
Note: See TracChangeset for help on using the changeset viewer.