001/**
002 * 
003 */
004package icy.system.audit;
005
006import java.io.IOException;
007import java.util.ArrayList;
008import java.util.HashMap;
009import java.util.List;
010import java.util.Map;
011import java.util.Map.Entry;
012
013import org.w3c.dom.Node;
014
015import icy.file.FileUtil;
016import icy.file.xml.XMLPersistent;
017import icy.file.xml.XMLPersistentHelper;
018import icy.network.NetworkUtil;
019import icy.plugin.PluginDescriptor;
020import icy.plugin.PluginDescriptor.PluginIdent;
021import icy.plugin.PluginLoader;
022import icy.plugin.abstract_.Plugin;
023import icy.system.IcyExceptionHandler;
024import icy.util.DateUtil;
025import icy.util.XMLUtil;
026
027/**
028 * @author Stephane
029 */
030public class AuditStorage implements XMLPersistent
031{
032    private static final String ID_PLUGIN = "plugin";
033
034    private static final String AUDIT_FILENAME = "icy_usage.xml";
035
036    private static final long SAVE_INTERVAL = 1000 * 60;
037
038    private final Map<PluginIdent, PluginStorage> pluginStats;
039    private long lastSaveTime;
040
041    public AuditStorage()
042    {
043        super();
044
045        pluginStats = new HashMap<PluginIdent, PluginStorage>();
046
047        try
048        {
049            // load usage data from XML file
050            XMLPersistentHelper.loadFromXML(this, FileUtil.getTempDirectory() + FileUtil.separator + AUDIT_FILENAME);
051            // clean obsoletes data
052            clean();
053        }
054        catch (Exception e)
055        {
056            System.out.println("Warning: can't reload usage statistics data.");
057            IcyExceptionHandler.showErrorMessage(e, false, false);
058        }
059
060        lastSaveTime = System.currentTimeMillis();
061    }
062
063    public void save()
064    {
065        try
066        {
067            // save XML data
068            XMLPersistentHelper.saveToXML(this, FileUtil.getTempDirectory() + FileUtil.separator + AUDIT_FILENAME);
069        }
070        catch (Exception e)
071        {
072            System.out.println("Warning: can't save usage statistics data.");
073            IcyExceptionHandler.showErrorMessage(e, false, false);
074        }
075    }
076
077    private void clean()
078    {
079        final List<PluginIdent> empties = new ArrayList<PluginIdent>();
080
081        synchronized (pluginStats)
082        {
083            // clean statistics
084            for (Entry<PluginIdent, PluginStorage> entry : pluginStats.entrySet())
085            {
086                entry.getValue().clean();
087                if (entry.getValue().isEmpty())
088                    empties.add(entry.getKey());
089            }
090
091            // remove empty ones
092            for (PluginIdent ident : empties)
093                pluginStats.remove(ident);
094        }
095    }
096
097    private void autoSave()
098    {
099        final long currentTime = System.currentTimeMillis();
100
101        // interval elapsed
102        if ((currentTime - lastSaveTime) > SAVE_INTERVAL)
103        {
104            // save statistics to disk
105            save();
106            lastSaveTime = currentTime;
107        }
108    }
109
110    private PluginStorage getStorage(PluginIdent ident, boolean autoCreate)
111    {
112        PluginStorage result;
113
114        synchronized (pluginStats)
115        {
116            result = pluginStats.get(ident);
117
118            if ((result == null) && autoCreate)
119            {
120                result = new PluginStorage();
121                pluginStats.put(ident, result);
122            }
123        }
124
125        return result;
126    }
127
128    public void pluginLaunched(Plugin plugin)
129    {
130        final PluginDescriptor descriptor;
131
132        if (plugin.isBundled())
133            descriptor = PluginLoader.getPlugin(plugin.getOwnerClassName());
134        else
135            descriptor = plugin.getDescriptor();
136
137        // ignore if no descriptor
138        if (descriptor == null)
139            return;
140        // ignore if missing version info
141        if (descriptor.getVersion().isEmpty())
142            return;
143        // ignore kernel plugins
144        if (descriptor.isKernelPlugin())
145            return;
146
147        // increment launch
148        getStorage(descriptor.getIdent(), true).incLaunch(DateUtil.keepDay(System.currentTimeMillis()));
149
150        // save to disk if needed
151        autoSave();
152    }
153
154    public void pluginInstanced(Plugin plugin)
155    {
156        final PluginDescriptor descriptor;
157
158        if (plugin.isBundled())
159            descriptor = PluginLoader.getPlugin(plugin.getOwnerClassName());
160        else
161            descriptor = plugin.getDescriptor();
162
163        // ignore if no descriptor
164        if (descriptor == null)
165            return;
166        // ignore if missing version info
167        if (descriptor.getVersion().isEmpty())
168            return;
169        // ignore kernel plugins
170        if (descriptor.isKernelPlugin())
171            return;
172
173        // increment instance
174        getStorage(descriptor.getIdent(), true).incInstance(DateUtil.keepDay(System.currentTimeMillis()));
175        // save to disk if needed
176        autoSave();
177    }
178
179    /**
180     * Upload statistics to website
181     */
182    public boolean upload(int id)
183    {
184        final List<PluginIdent> dones = new ArrayList<PluginIdent>();
185        final List<Entry<PluginIdent, PluginStorage>> entries;
186
187        synchronized (pluginStats)
188        {
189            entries = new ArrayList<Entry<PluginIdent, PluginStorage>>(pluginStats.entrySet());
190        }
191
192        try
193        {
194            for (Entry<PluginIdent, PluginStorage> entry : entries)
195            {
196                if (entry.getValue().upload(id, entry.getKey()))
197                    dones.add(entry.getKey());
198
199                // interrupt upload
200                if (Thread.interrupted())
201                    break;
202            }
203        }
204        finally
205        {
206            // remove stats which has been correctly uploaded
207            synchronized (pluginStats)
208            {
209                for (PluginIdent ident : dones)
210                    pluginStats.remove(ident);
211            }
212        }
213
214        return pluginStats.isEmpty();
215    }
216
217    @Override
218    public boolean loadFromXML(Node node)
219    {
220        if (node == null)
221            return false;
222
223        synchronized (pluginStats)
224        {
225            pluginStats.clear();
226            for (Node n : XMLUtil.getChildren(node, ID_PLUGIN))
227            {
228                final PluginIdent ident = new PluginIdent();
229                final PluginStorage storage = new PluginStorage();
230
231                ident.loadFromXMLShort(n);
232                storage.loadFromXML(n);
233
234                pluginStats.put(ident, storage);
235            }
236        }
237
238        return true;
239    }
240
241    @Override
242    public boolean saveToXML(Node node)
243    {
244        if (node == null)
245            return false;
246
247        XMLUtil.removeAllChildren(node);
248
249        synchronized (pluginStats)
250        {
251            for (Entry<PluginIdent, PluginStorage> entry : pluginStats.entrySet())
252            {
253                final Node n = XMLUtil.addElement(node, ID_PLUGIN);
254
255                entry.getKey().saveToXMLShort(n);
256                entry.getValue().saveToXML(n);
257            }
258        }
259
260        return true;
261    }
262
263    private class PluginStorage implements XMLPersistent
264    {
265        private static final String ID_CLASSNAME = PluginIdent.ID_CLASSNAME;
266        private static final String ID_VERSION = PluginIdent.ID_VERSION;
267
268        private static final String ID_LAUNCH = "launch";
269        private static final String ID_INSTANCE = "instance";
270
271        private static final String ID_STATS_LAUNCH = "stats_" + ID_LAUNCH;
272        private static final String ID_STATS_INSTANCE = "stats_" + ID_INSTANCE;
273        private static final String ID_DATE = "date";
274        private static final String ID_VALUE = "value";
275
276        private static final long DAY_TO_KEEP = 30L;
277
278        private final Map<Long, Long> launchStats;
279        private final Map<Long, Long> instanceStats;
280
281        public PluginStorage()
282        {
283            super();
284
285            launchStats = new HashMap<Long, Long>();
286            instanceStats = new HashMap<Long, Long>();
287        }
288
289        public void clean()
290        {
291            List<Long> olds = new ArrayList<Long>();
292            final long dayInterval = 1000 * 60 * 60 * 24;
293            final long timeLimit = System.currentTimeMillis() - (DAY_TO_KEEP * dayInterval);
294
295            // find obsoletes entries
296            olds.clear();
297            for (Long date : launchStats.keySet())
298                if (date.longValue() < timeLimit)
299                    olds.add(date);
300
301            // remove them
302            for (Long date : olds)
303                launchStats.remove(date);
304
305            // find obsoletes entries
306            olds.clear();
307            for (Long date : instanceStats.keySet())
308                if (date.longValue() < timeLimit)
309                    olds.add(date);
310
311            // remove them
312            for (Long date : olds)
313                instanceStats.remove(date);
314        }
315
316        public boolean isEmpty()
317        {
318            return launchStats.isEmpty() && instanceStats.isEmpty();
319        }
320
321        public long getLaunch(Long date)
322        {
323            final Long result = launchStats.get(date);
324
325            if (result == null)
326                return 0L;
327
328            return result.longValue();
329        }
330
331        public long getInstance(Long date)
332        {
333            final Long result = instanceStats.get(date);
334
335            if (result == null)
336                return 0L;
337
338            return result.longValue();
339        }
340
341        public void incLaunch(long date)
342        {
343            final Long key = Long.valueOf(date);
344            launchStats.put(key, Long.valueOf(getLaunch(key) + 1L));
345        }
346
347        public void incInstance(long date)
348        {
349            final Long key = Long.valueOf(date);
350            instanceStats.put(key, Long.valueOf(getInstance(key) + 1L));
351        }
352
353        private Map<String, String> getIdParam(int id)
354        {
355            // id ok ?
356            if (id != -1)
357            {
358                final Map<String, String> values = new HashMap<String, String>();
359
360                // set id
361                values.put(Audit.ID_ICY_ID, Integer.toString(id));
362
363                return values;
364            }
365
366            return null;
367        }
368
369        public boolean upload(int id, PluginIdent ident)
370        {
371            // init params
372            final Map<String, String> params = getIdParam(id);
373            int offset;
374
375            // set plugin identity
376            params.put(ID_CLASSNAME, ident.getClassName());
377            params.put(ID_VERSION, ident.getVersion().toString());
378
379            offset = 0;
380            // build params for launch statistic
381            for (Entry<Long, Long> entry : launchStats.entrySet())
382            {
383                // set date
384                params.put(ID_STATS_LAUNCH + "[" + offset + "][" + ID_DATE + "]", entry.getKey().toString());
385                // set value
386                params.put(ID_STATS_LAUNCH + "[" + offset + "][" + ID_VALUE + "]", entry.getValue().toString());
387                offset++;
388            }
389
390            offset = 0;
391            // build params for instance statistic
392            for (Entry<Long, Long> entry : instanceStats.entrySet())
393            {
394                // set date
395                params.put(ID_STATS_INSTANCE + "[" + offset + "][" + ID_DATE + "]", entry.getKey().toString());
396                // set value
397                params.put(ID_STATS_INSTANCE + "[" + offset + "][" + ID_VALUE + "]", entry.getValue().toString());
398                offset++;
399            }
400
401            try
402            {
403                // null return means website did not accepted them...
404                if (NetworkUtil.postData(Audit.URL_AUDIT_PLUGIN, params) == null)
405                    return false;
406            }
407            catch (IOException e)
408            {
409                return false;
410            }
411
412            // clear stats just to be sure to not send them twice
413            launchStats.clear();
414            instanceStats.clear();
415
416            return true;
417        }
418
419        @Override
420        public boolean loadFromXML(Node node)
421        {
422            if (node == null)
423                return false;
424
425            launchStats.clear();
426            for (Node n : XMLUtil.getChildren(node, ID_LAUNCH))
427            {
428                final long date = XMLUtil.getElementLongValue(n, ID_DATE, 0L);
429                final long value = XMLUtil.getElementLongValue(n, ID_VALUE, 0L);
430
431                launchStats.put(Long.valueOf(date), Long.valueOf(value));
432            }
433
434            instanceStats.clear();
435            for (Node n : XMLUtil.getChildren(node, ID_INSTANCE))
436            {
437                final long date = XMLUtil.getElementLongValue(n, ID_DATE, 0L);
438                final long value = XMLUtil.getElementLongValue(n, ID_VALUE, 0L);
439
440                instanceStats.put(Long.valueOf(date), Long.valueOf(value));
441            }
442
443            return true;
444        }
445
446        @Override
447        public boolean saveToXML(Node node)
448        {
449            if (node == null)
450                return false;
451
452            for (Entry<Long, Long> entry : launchStats.entrySet())
453            {
454                final Node n = XMLUtil.addElement(node, ID_LAUNCH);
455
456                XMLUtil.setElementLongValue(n, ID_DATE, entry.getKey().longValue());
457                XMLUtil.setElementLongValue(n, ID_VALUE, entry.getValue().longValue());
458            }
459
460            for (Entry<Long, Long> entry : instanceStats.entrySet())
461            {
462                final Node n = XMLUtil.addElement(node, ID_INSTANCE);
463
464                XMLUtil.setElementLongValue(n, ID_DATE, entry.getKey().longValue());
465                XMLUtil.setElementLongValue(n, ID_VALUE, entry.getValue().longValue());
466            }
467
468            return true;
469        }
470    }
471
472}