001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.plugin;
020
021import icy.main.Icy;
022import icy.network.NetworkUtil;
023import icy.network.URLUtil;
024import icy.plugin.PluginDescriptor.PluginIdent;
025import icy.plugin.PluginDescriptor.PluginNameSorter;
026import icy.plugin.PluginDescriptor.PluginOnlineIdent;
027import icy.preferences.PluginPreferences;
028import icy.preferences.RepositoryPreferences;
029import icy.preferences.RepositoryPreferences.RepositoryInfo;
030import icy.system.IcyExceptionHandler;
031import icy.system.thread.SingleProcessor;
032import icy.system.thread.ThreadUtil;
033import icy.util.XMLUtil;
034
035import java.util.ArrayList;
036import java.util.Collections;
037import java.util.EventListener;
038import java.util.HashMap;
039import java.util.List;
040import java.util.Map;
041
042import javax.swing.event.EventListenerList;
043
044import org.w3c.dom.Document;
045import org.w3c.dom.Node;
046
047/**
048 * @author stephane
049 */
050public class PluginRepositoryLoader
051{
052    public static interface PluginRepositoryLoaderListener extends EventListener
053    {
054        public void pluginRepositeryLoaderChanged(PluginDescriptor plugin);
055    }
056
057    private class Loader implements Runnable
058    {
059        public Loader()
060        {
061            super();
062        }
063
064        @Override
065        public void run()
066        {
067            final List<PluginDescriptor> newPlugins = new ArrayList<PluginDescriptor>();
068
069            try
070            {
071                final List<RepositoryInfo> repositories = RepositoryPreferences.getRepositeries();
072
073                // load online plugins from all active repositories
074                for (RepositoryInfo repoInfo : repositories)
075                {
076                    // reload requested --> stop current loading
077                    if (processor.hasWaitingTasks())
078                        return;
079
080                    if (repoInfo.isEnabled())
081                    {
082                        final List<PluginDescriptor> pluginsRepos = loadInternal(repoInfo);
083
084                        if (pluginsRepos == null)
085                        {
086                            failed = true;
087                            return;
088                        }
089
090                        newPlugins.addAll(pluginsRepos);
091                    }
092                }
093
094                // sort list on plugin class name
095                Collections.sort(newPlugins, PluginNameSorter.instance);
096
097                plugins = newPlugins;
098            }
099            catch (Exception e)
100            {
101                IcyExceptionHandler.showErrorMessage(e, true);
102                failed = true;
103                return;
104            }
105
106            // notify basic data has been loaded
107            loaded = true;
108            changed(null);
109        }
110    }
111
112    private static final String ID_ROOT = "plugins";
113    private static final String ID_PLUGIN = "plugin";
114    // private static final String ID_PATH = "path";
115
116    /**
117     * static class
118     */
119    private static final PluginRepositoryLoader instance = new PluginRepositoryLoader();
120
121    /**
122     * Online plugin list
123     */
124    List<PluginDescriptor> plugins;
125
126    /**
127     * listeners
128     */
129    private final EventListenerList listeners;
130
131    /**
132     * internals
133     */
134    boolean loaded;
135    boolean failed;
136
137    private final Loader loader;
138    final SingleProcessor processor;
139
140    /**
141     * static class
142     */
143    private PluginRepositoryLoader()
144    {
145        super();
146
147        plugins = new ArrayList<PluginDescriptor>();
148        listeners = new EventListenerList();
149
150        loader = new Loader();
151        processor = new SingleProcessor(true, "Online Plugin Loader");
152
153        loaded = false;
154        // initial loading
155        load();
156    }
157
158    /**
159     * Return the plugins identifier list from a repository URL
160     */
161    public static List<PluginOnlineIdent> getPluginIdents(RepositoryInfo repos)
162    {
163        String address = repos.getLocation();
164        final boolean networkAddr = URLUtil.isNetworkURL(address);
165        final boolean betaAllowed = PluginPreferences.getAllowBeta();
166
167        if (networkAddr && repos.getSupportParam())
168        {
169            // prepare parameters for plugin list request
170            final Map<String, String> values = new HashMap<String, String>();
171
172            // add kernel information parameter
173            values.put(NetworkUtil.ID_KERNELVERSION, Icy.version.toString());
174            // add beta allowed information parameter
175            values.put(NetworkUtil.ID_BETAALLOWED, Boolean.toString(betaAllowed));
176            // concat to address
177            address += "?" + NetworkUtil.getContentString(values);
178        }
179
180        // load the XML file
181        final Document document = XMLUtil.loadDocument(address, repos.getAuthenticationInfo(), false);
182
183        // error
184        if (document == null)
185        {
186            if (networkAddr && !NetworkUtil.hasInternetAccess())
187                System.out.println("You are not connected to internet.");
188
189            return null;
190        }
191
192        final List<PluginOnlineIdent> result = new ArrayList<PluginOnlineIdent>();
193        // get plugins node
194        final Node pluginsNode = XMLUtil.getElement(document.getDocumentElement(), ID_ROOT);
195
196        // plugins node found
197        if (pluginsNode != null)
198        {
199            // ident nodes
200            final List<Node> nodes = XMLUtil.getChildren(pluginsNode, ID_PLUGIN);
201
202            for (Node node : nodes)
203            {
204                final PluginOnlineIdent ident = new PluginOnlineIdent();
205
206                ident.loadFromXML(node);
207
208                // accept only if not empty
209                if (!ident.isEmpty())
210                {
211                    // accept only if required kernel version is ok and beta accepted
212                    if (ident.getRequiredKernelVersion().isLowerOrEqual(Icy.version)
213                            && (betaAllowed || (!ident.getVersion().isBeta())))
214                    {
215                        // check if we have several version of the same plugin
216                        final int ind = PluginIdent.getIndex(result, ident.getClassName());
217                        // other version found ?
218                        if (ind != -1)
219                        {
220                            // replace old version if needed
221                            if (result.get(ind).isOlderOrEqual(ident))
222                                result.set(ind, ident);
223                        }
224                        else
225                            result.add(ident);
226                    }
227                }
228            }
229        }
230
231        return result;
232    }
233
234    /**
235     * Do loading process.
236     */
237    private void load()
238    {
239        loaded = false;
240        failed = false;
241
242        processor.submit(loader);
243    }
244
245    /**
246     * Reload all plugins from all active repositories (old list is cleared).<br>
247     * Asynchronous process, use {@link #waitLoaded()} method to wait for basic data to be loaded.
248     */
249    public static synchronized void reload()
250    {
251        instance.load();
252    }
253
254    /**
255     * Load the list of online plugins located at specified repository
256     */
257    // public static void load(final RepositoryInfo repos, boolean asynch, final boolean
258    // loadDescriptor,
259    // final boolean loadImages)
260    // {
261    // instance.loadSingleRunner.setParameters(repos, loadDescriptor, loadImages);
262    //
263    // if (asynch)
264    // ThreadUtil.bgRunSingle(instance.loadAllRunner);
265    // else
266    // instance.loadAllRunner.run();
267    // }
268
269    /**
270     * Load and return the list of online plugins located at specified repository
271     */
272    static List<PluginDescriptor> loadInternal(RepositoryInfo repos)
273    {
274        // we start by loading only identifier part
275        final List<PluginOnlineIdent> idents = getPluginIdents(repos);
276
277        // error while retrieving identifiers ?
278        if (idents == null)
279        {
280            System.out.println("Can't access repository '" + repos.getName() + "'");
281            return null;
282        }
283
284        final List<PluginDescriptor> result = new ArrayList<PluginDescriptor>();
285
286        for (PluginOnlineIdent ident : idents)
287        {
288            try
289            {
290                result.add(new PluginDescriptor(ident, repos));
291            }
292            catch (Exception e)
293            {
294                System.out.println("PluginRepositoryLoader.load('" + repos.getLocation() + "') error :");
295                IcyExceptionHandler.showErrorMessage(e, false);
296            }
297        }
298
299        return result;
300    }
301
302    /**
303     * @return the pluginList
304     */
305    public static ArrayList<PluginDescriptor> getPlugins()
306    {
307        synchronized (instance.plugins)
308        {
309            return new ArrayList<PluginDescriptor>(instance.plugins);
310        }
311    }
312
313    public static PluginDescriptor getPlugin(String className)
314    {
315        synchronized (instance.plugins)
316        {
317            return PluginDescriptor.getPlugin(instance.plugins, className);
318        }
319    }
320
321    public static List<PluginDescriptor> getPlugins(String className)
322    {
323        synchronized (instance.plugins)
324        {
325            return PluginDescriptor.getPlugins(instance.plugins, className);
326        }
327    }
328
329    /**
330     * Return the plugins list from the specified repository
331     */
332    public static List<PluginDescriptor> getPlugins(RepositoryInfo repos)
333    {
334        final List<PluginDescriptor> result = new ArrayList<PluginDescriptor>();
335
336        synchronized (instance.plugins)
337        {
338            for (PluginDescriptor plugin : instance.plugins)
339                if (plugin.getRepository().equals(repos))
340                    result.add(plugin);
341        }
342
343        return result;
344    }
345
346    /**
347     * @return true if loader is loading the basic informations
348     */
349    public static boolean isLoading()
350    {
351        return instance.processor.isProcessing();
352    }
353
354    /**
355     * @return true if basic informations (class names, versions...) are loaded.
356     */
357    public static boolean isLoaded()
358    {
359        return instance.failed || instance.loaded;
360    }
361
362    /**
363     * Wait until basic informations are loaded.
364     */
365    public static void waitLoaded()
366    {
367        while (!isLoaded())
368            ThreadUtil.sleep(10);
369    }
370
371    /**
372     * @deprecated use {@link #isLoaded()} instead.
373     */
374    @Deprecated
375    public static boolean isBasicLoaded()
376    {
377        return isLoaded();
378    }
379
380    /**
381     * @deprecated descriptor loading is now done per descriptor when needed
382     */
383    @Deprecated
384    public static boolean isDescriptorsLoaded()
385    {
386        return true;
387    }
388
389    /**
390     * @deprecated image loading is now done per descriptor when needed
391     */
392    @Deprecated
393    public static boolean isImagesLoaded()
394    {
395        return true;
396    }
397
398    /**
399     * @deprecated use {@link #waitLoaded()} instead.
400     */
401    @Deprecated
402    public static void waitBasicLoaded()
403    {
404        waitLoaded();
405    }
406
407    /**
408     * @deprecated descriptor loading is now done per descriptor when needed
409     */
410    @Deprecated
411    public static void waitDescriptorsLoaded()
412    {
413        // do nothing
414    }
415
416    /**
417     * Returns true if an error occurred during the plugin loading process.
418     */
419    public static boolean failed()
420    {
421        return instance.failed;
422    }
423
424    /**
425     * Plugin list has changed
426     */
427    void changed(PluginDescriptor plugin)
428    {
429        fireEvent(plugin);
430    }
431
432    /**
433     * Add a listener
434     * 
435     * @param listener
436     */
437    public static void addListener(PluginRepositoryLoaderListener listener)
438    {
439        synchronized (instance.listeners)
440        {
441            instance.listeners.add(PluginRepositoryLoaderListener.class, listener);
442        }
443    }
444
445    /**
446     * Remove a listener
447     * 
448     * @param listener
449     */
450    public static void removeListener(PluginRepositoryLoaderListener listener)
451    {
452        synchronized (instance.listeners)
453        {
454            instance.listeners.remove(PluginRepositoryLoaderListener.class, listener);
455        }
456    }
457
458    /**
459     * fire event
460     * 
461     * @param plugin
462     */
463    private void fireEvent(PluginDescriptor plugin)
464    {
465        for (PluginRepositoryLoaderListener listener : listeners.getListeners(PluginRepositoryLoaderListener.class))
466            listener.pluginRepositeryLoaderChanged(plugin);
467    }
468}