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}