1 /* 2 * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.util.prefs; 27 28 import java.util.HashMap; 29 import java.util.HashSet; 30 import java.util.Iterator; 31 import java.util.Timer; 32 import java.util.TimerTask; 33 import java.lang.ref.WeakReference; 34 35 36 /* 37 MacOSXPreferencesFile synchronization: 38 39 Everything is synchronized on MacOSXPreferencesFile.class. This prevents: 40 * simultaneous updates to cachedFiles or changedFiles 41 * simultaneous creation of two objects for the same name+user+host triplet 42 * simultaneous modifications to the same file 43 * modifications during syncWorld/flushWorld 44 * (in MacOSXPreferences.removeNodeSpi()) modification or sync during 45 multi-step node removal process 46 ... among other things. 47 */ 48 /* 49 Timers. There are two timers that control synchronization of prefs data to 50 and from disk. 51 52 * Sync timer periodically calls syncWorld() to force external disk changes 53 (e.g. from another VM) into the memory cache. The sync timer runs even 54 if there are no outstanding local changes. The sync timer syncs all live 55 MacOSXPreferencesFile objects (the cachedFiles list). 56 The sync timer period is controlled by the java.util.prefs.syncInterval 57 property (same as FileSystemPreferences). By default there is *no* 58 sync timer (unlike FileSystemPreferences); it is only enabled if the 59 syncInterval property is set. The minimum interval is 5 seconds. 60 61 * Flush timer calls flushWorld() to force local changes to disk. 62 The flush timer is scheduled to fire some time after each pref change, 63 unless it's already scheduled to fire before that. syncWorld and 64 flushWorld will cancel any outstanding flush timer as unnecessary. 65 The flush timer flushes all changed files (the changedFiles list). 66 The time between pref write and flush timer call is controlled by the 67 java.util.prefs.flushDelay property (unlike FileSystemPreferences). 68 The default is 60 seconds and the minimum is 5 seconds. 69 70 The flush timer's behavior is required by the Java Preferences spec 71 ("changes will eventually propagate to the persistent backing store with 72 an implementation-dependent delay"). The sync timer is not required by 73 the spec (multiple VMs are only required to not corrupt the prefs), but 74 the periodic sync is implemented by FileSystemPreferences and may be 75 useful to some programs. The sync timer is disabled by default because 76 it's expensive and is usually not necessary. 77 */ 78 79 class MacOSXPreferencesFile { 80 81 static { 82 java.security.AccessController.doPrivileged( 83 new java.security.PrivilegedAction<Void>() { 84 public Void run() { 85 System.loadLibrary("prefs"); 86 return null; 87 } 88 }); 89 } 90 91 private class FlushTask extends TimerTask { 92 public void run() { 93 MacOSXPreferencesFile.flushWorld(); 94 } 95 } 96 97 private class SyncTask extends TimerTask { 98 public void run() { 99 MacOSXPreferencesFile.syncWorld(); 100 } 101 } 102 103 // Maps string -> weak reference to MacOSXPreferencesFile 104 private static HashMap<String, WeakReference<MacOSXPreferencesFile>> 105 cachedFiles; 106 // Files that may have unflushed changes 107 private static HashSet<MacOSXPreferencesFile> changedFiles; 108 109 110 // Timer and pending sync and flush tasks (which are both scheduled 111 // on the same timer) 112 private static Timer timer = null; 113 private static FlushTask flushTimerTask = null; 114 private static long flushDelay = -1; // in seconds (min 5, default 60) 115 private static long syncInterval = -1; // (min 5, default negative == off) 116 117 private String appName; 118 private long user; 119 private long host; 120 121 String name() { return appName; } 122 long user() { return user; } 123 long host() { return host; } 124 125 // private constructor - use factory method getFile() instead 126 private MacOSXPreferencesFile(String newName, long newUser, long newHost) 127 { 128 appName = newName; 129 user = newUser; 130 host = newHost; 131 } 132 133 // Factory method 134 // Always returns the same object for the given name+user+host 135 static synchronized MacOSXPreferencesFile 136 getFile(String newName, boolean isUser) 137 { 138 MacOSXPreferencesFile result = null; 139 140 if (cachedFiles == null) 141 cachedFiles = new HashMap<>(); 142 143 String hashkey = 144 newName + String.valueOf(isUser); 145 WeakReference<MacOSXPreferencesFile> hashvalue = cachedFiles.get(hashkey); 146 if (hashvalue != null) { 147 result = hashvalue.get(); 148 } 149 if (result == null) { 150 // Java user node == CF current user, any host 151 // Java system node == CF any user, current host 152 result = new MacOSXPreferencesFile(newName, 153 isUser ? cfCurrentUser : cfAnyUser, 154 isUser ? cfAnyHost : cfCurrentHost); 155 cachedFiles.put(hashkey, new WeakReference<MacOSXPreferencesFile>(result)); 156 } 157 158 // Don't schedule this file for flushing until some nodes or 159 // keys are added to it. 160 161 // Do set up the sync timer if requested; sync timer affects reads 162 // as well as writes. 163 initSyncTimerIfNeeded(); 164 165 return result; 166 } 167 168 169 // Write all prefs changes to disk and clear all cached prefs values 170 // (so the next read will read from disk). 171 static synchronized boolean syncWorld() 172 { 173 boolean ok = true; 174 175 if (cachedFiles != null && !cachedFiles.isEmpty()) { 176 Iterator<WeakReference<MacOSXPreferencesFile>> iter = 177 cachedFiles.values().iterator(); 178 while (iter.hasNext()) { 179 WeakReference<MacOSXPreferencesFile> ref = iter.next(); 180 MacOSXPreferencesFile f = ref.get(); 181 if (f != null) { 182 if (!f.synchronize()) ok = false; 183 } else { 184 iter.remove(); 185 } 186 } 187 } 188 189 // Kill any pending flush 190 if (flushTimerTask != null) { 191 flushTimerTask.cancel(); 192 flushTimerTask = null; 193 } 194 195 // Clear changed file list. The changed files were guaranteed to 196 // have been in the cached file list (because there was a strong 197 // reference from changedFiles. 198 if (changedFiles != null) changedFiles.clear(); 199 200 return ok; 201 } 202 203 204 // Sync only current user preferences 205 static synchronized boolean syncUser() { 206 boolean ok = true; 207 if (cachedFiles != null && !cachedFiles.isEmpty()) { 208 Iterator<WeakReference<MacOSXPreferencesFile>> iter = 209 cachedFiles.values().iterator(); 210 while (iter.hasNext()) { 211 WeakReference<MacOSXPreferencesFile> ref = iter.next(); 212 MacOSXPreferencesFile f = ref.get(); 213 if (f != null && f.user == cfCurrentUser) { 214 if (!f.synchronize()) { 215 ok = false; 216 } 217 } else { 218 iter.remove(); 219 } 220 } 221 } 222 // Remove synchronized file from changed file list. The changed files were 223 // guaranteed to have been in the cached file list (because there was a strong 224 // reference from changedFiles. 225 if (changedFiles != null) { 226 Iterator<MacOSXPreferencesFile> iterChanged = changedFiles.iterator(); 227 while (iterChanged.hasNext()) { 228 MacOSXPreferencesFile f = iterChanged.next(); 229 if (f != null && f.user == cfCurrentUser) 230 iterChanged.remove(); 231 } 232 } 233 return ok; 234 } 235 236 //Flush only current user preferences 237 static synchronized boolean flushUser() { 238 boolean ok = true; 239 if (changedFiles != null && !changedFiles.isEmpty()) { 240 Iterator<MacOSXPreferencesFile> iterator = changedFiles.iterator(); 241 while(iterator.hasNext()) { 242 MacOSXPreferencesFile f = iterator.next(); 243 if (f.user == cfCurrentUser) { 244 if (!f.synchronize()) 245 ok = false; 246 else 247 iterator.remove(); 248 } 249 } 250 } 251 return ok; 252 } 253 254 // Write all prefs changes to disk, but do not clear all cached prefs 255 // values. Also kills any scheduled flush task. 256 // There's no CFPreferencesFlush() (<rdar://problem/3049129>), so lots of cached prefs 257 // are cleared anyway. 258 static synchronized boolean flushWorld() 259 { 260 boolean ok = true; 261 262 if (changedFiles != null && !changedFiles.isEmpty()) { 263 for (MacOSXPreferencesFile f : changedFiles) { 264 if (!f.synchronize()) 265 ok = false; 266 } 267 changedFiles.clear(); 268 } 269 270 if (flushTimerTask != null) { 271 flushTimerTask.cancel(); 272 flushTimerTask = null; 273 } 274 275 return ok; 276 } 277 278 // Mark this prefs file as changed. The changes will be flushed in 279 // at most flushDelay() seconds. 280 // Must be called when synchronized on MacOSXPreferencesFile.class 281 private void markChanged() 282 { 283 // Add this file to the changed file list 284 if (changedFiles == null) 285 changedFiles = new HashSet<>(); 286 changedFiles.add(this); 287 288 // Schedule a new flush and a shutdown hook, if necessary 289 if (flushTimerTask == null) { 290 flushTimerTask = new FlushTask(); 291 timer().schedule(flushTimerTask, flushDelay() * 1000); 292 } 293 } 294 295 // Return the flush delay, initializing from a property if necessary. 296 private static synchronized long flushDelay() 297 { 298 if (flushDelay == -1) { 299 try { 300 // flush delay >= 5, default 60 301 flushDelay = Math.max(5, Integer.parseInt(System.getProperty("java.util.prefs.flushDelay", "60"))); 302 } catch (NumberFormatException e) { 303 flushDelay = 60; 304 } 305 } 306 return flushDelay; 307 } 308 309 // Initialize and run the sync timer, if the sync timer property is set 310 // and the sync timer hasn't already been started. 311 private static synchronized void initSyncTimerIfNeeded() 312 { 313 // syncInterval: -1 is uninitialized, other negative is off, 314 // positive is seconds between syncs (min 5). 315 316 if (syncInterval == -1) { 317 try { 318 syncInterval = Integer.parseInt(System.getProperty("java.util.prefs.syncInterval", "-2")); 319 if (syncInterval >= 0) { 320 // minimum of 5 seconds 321 syncInterval = Math.max(5, syncInterval); 322 } else { 323 syncInterval = -2; // default off 324 } 325 } catch (NumberFormatException e) { 326 syncInterval = -2; // bad property value - default off 327 } 328 329 if (syncInterval > 0) { 330 timer().schedule(new TimerTask() { 331 @Override 332 public void run() { 333 MacOSXPreferencesFile.syncWorld();} 334 }, syncInterval * 1000, syncInterval * 1000); 335 } else { 336 // syncInterval property not set. No sync timer ever. 337 } 338 } 339 } 340 341 // Return the timer used for flush and sync, creating it if necessary. 342 private static synchronized Timer timer() 343 { 344 if (timer == null) { 345 timer = new Timer(true); // daemon 346 Thread flushThread = new Thread() { 347 @Override 348 public void run() { 349 flushWorld(); 350 } 351 }; 352 /* Set context class loader to null in order to avoid 353 * keeping a strong reference to an application classloader. 354 */ 355 flushThread.setContextClassLoader(null); 356 Runtime.getRuntime().addShutdownHook(flushThread); 357 } 358 return timer; 359 } 360 361 362 // Node manipulation 363 boolean addNode(String path) 364 { 365 synchronized(MacOSXPreferencesFile.class) { 366 markChanged(); 367 return addNode(path, appName, user, host); 368 } 369 } 370 371 void removeNode(String path) 372 { 373 synchronized(MacOSXPreferencesFile.class) { 374 markChanged(); 375 removeNode(path, appName, user, host); 376 } 377 } 378 379 boolean addChildToNode(String path, String child) 380 { 381 synchronized(MacOSXPreferencesFile.class) { 382 markChanged(); 383 return addChildToNode(path, child+"/", appName, user, host); 384 } 385 } 386 387 void removeChildFromNode(String path, String child) 388 { 389 synchronized(MacOSXPreferencesFile.class) { 390 markChanged(); 391 removeChildFromNode(path, child+"/", appName, user, host); 392 } 393 } 394 395 396 // Key manipulation 397 void addKeyToNode(String path, String key, String value) 398 { 399 synchronized(MacOSXPreferencesFile.class) { 400 markChanged(); 401 addKeyToNode(path, key, value, appName, user, host); 402 } 403 } 404 405 void removeKeyFromNode(String path, String key) 406 { 407 synchronized(MacOSXPreferencesFile.class) { 408 markChanged(); 409 removeKeyFromNode(path, key, appName, user, host); 410 } 411 } 412 413 String getKeyFromNode(String path, String key) 414 { 415 synchronized(MacOSXPreferencesFile.class) { 416 return getKeyFromNode(path, key, appName, user, host); 417 } 418 } 419 420 421 // Enumerators 422 String[] getChildrenForNode(String path) 423 { 424 synchronized(MacOSXPreferencesFile.class) { 425 return getChildrenForNode(path, appName, user, host); 426 } 427 } 428 429 String[] getKeysForNode(String path) 430 { 431 synchronized(MacOSXPreferencesFile.class) { 432 return getKeysForNode(path, appName, user, host); 433 } 434 } 435 436 437 // Synchronization 438 boolean synchronize() 439 { 440 synchronized(MacOSXPreferencesFile.class) { 441 return synchronize(appName, user, host); 442 } 443 } 444 445 446 // CF functions 447 // Must be called when synchronized on MacOSXPreferencesFile.class 448 private static final native boolean 449 addNode(String path, String name, long user, long host); 450 private static final native void 451 removeNode(String path, String name, long user, long host); 452 private static final native boolean 453 addChildToNode(String path, String child, 454 String name, long user, long host); 455 private static final native void 456 removeChildFromNode(String path, String child, 457 String name, long user, long host); 458 private static final native void 459 addKeyToNode(String path, String key, String value, 460 String name, long user, long host); 461 private static final native void 462 removeKeyFromNode(String path, String key, 463 String name, long user, long host); 464 private static final native String 465 getKeyFromNode(String path, String key, 466 String name, long user, long host); 467 private static final native String[] 468 getChildrenForNode(String path, String name, long user, long host); 469 private static final native String[] 470 getKeysForNode(String path, String name, long user, long host); 471 private static final native boolean 472 synchronize(String name, long user, long host); 473 474 // CFPreferences host and user values (CFStringRefs) 475 private static long cfCurrentUser = currentUser(); 476 private static long cfAnyUser = anyUser(); 477 private static long cfCurrentHost = currentHost(); 478 private static long cfAnyHost = anyHost(); 479 480 // CFPreferences constant accessors 481 private static final native long currentUser(); 482 private static final native long anyUser(); 483 private static final native long currentHost(); 484 private static final native long anyHost(); 485 } 486