1 /* 2 * Copyright (c) 2011, 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(new sun.security.action.LoadLibraryAction("osx")); 83 } 84 85 private class FlushTask extends TimerTask { 86 public void run() { 87 MacOSXPreferencesFile.flushWorld(); 88 } 89 } 90 91 private class SyncTask extends TimerTask { 92 public void run() { 93 MacOSXPreferencesFile.syncWorld(); 94 } 95 } 96 97 // Maps string -> weak reference to MacOSXPreferencesFile 98 private static HashMap cachedFiles = null; 99 // Files that may have unflushed changes 100 private static HashSet changedFiles = null; 101 102 103 // Timer and pending sync and flush tasks (which are both scheduled 104 // on the same timer) 105 private static Timer timer = null; 106 private static FlushTask flushTimerTask = null; 107 private static long flushDelay = -1; // in seconds (min 5, default 60) 108 private static long syncInterval = -1; // (min 5, default negative == off) 109 110 private String appName; 111 private long user; 112 private long host; 113 114 String name() { return appName; } 115 long user() { return user; } 116 long host() { return host; } 117 118 // private contructor - use factory method getFile() instead 119 private MacOSXPreferencesFile(String newName, long newUser, long newHost) 120 { 121 appName = newName; 122 user = newUser; 123 host = newHost; 124 } 125 126 // Factory method 127 // Always returns the same object for the given name+user+host 128 static synchronized MacOSXPreferencesFile 129 getFile(String newName, boolean isUser) 130 { 131 MacOSXPreferencesFile result = null; 132 133 if (cachedFiles == null) cachedFiles = new HashMap(); 134 135 String hashkey = 136 newName + String.valueOf(isUser); 137 WeakReference hashvalue = (WeakReference)cachedFiles.get(hashkey); 138 if (hashvalue != null) { 139 result = (MacOSXPreferencesFile)hashvalue.get(); 140 } 141 if (result == null) { 142 // Java user node == CF current user, any host 143 // Java system node == CF any user, current host 144 result = new MacOSXPreferencesFile(newName, 145 isUser ? cfCurrentUser : cfAnyUser, 146 isUser ? cfAnyHost : cfCurrentHost); 147 cachedFiles.put(hashkey, new WeakReference(result)); 148 } 149 150 // Don't schedule this file for flushing until some nodes or 151 // keys are added to it. 152 153 // Do set up the sync timer if requested; sync timer affects reads 154 // as well as writes. 155 initSyncTimerIfNeeded(); 156 157 return result; 158 } 159 160 161 // Write all prefs changes to disk and clear all cached prefs values 162 // (so the next read will read from disk). 163 static synchronized boolean syncWorld() 164 { 165 boolean ok = true; 166 167 if (cachedFiles != null && !cachedFiles.isEmpty()) { 168 Iterator iter = cachedFiles.values().iterator(); 169 while (iter.hasNext()) { 170 WeakReference ref = (WeakReference)iter.next(); 171 MacOSXPreferencesFile f = (MacOSXPreferencesFile)ref.get(); 172 if (f != null) { 173 if (!f.synchronize()) ok = false; 174 } else { 175 iter.remove(); 176 } 177 } 178 } 179 180 // Kill any pending flush 181 if (flushTimerTask != null) { 182 flushTimerTask.cancel(); 183 flushTimerTask = null; 184 } 185 186 // Clear changed file list. The changed files were guaranteed to 187 // have been in the cached file list (because there was a strong 188 // reference from changedFiles. 189 if (changedFiles != null) changedFiles.clear(); 190 191 return ok; 192 } 193 194 195 // Sync only current user preferences 196 static synchronized boolean syncUser() { 197 boolean ok = true; 198 if (cachedFiles != null && !cachedFiles.isEmpty()) { 199 Iterator<WeakReference> iter = cachedFiles.values().iterator(); 200 while (iter.hasNext()) { 201 WeakReference ref = iter.next(); 202 MacOSXPreferencesFile f = (MacOSXPreferencesFile)ref.get(); 203 if (f != null && f.user == cfCurrentUser) { 204 if (!f.synchronize()) ok = false; 205 } else { 206 iter.remove(); 207 } 208 } 209 } 210 // Clear changed file list. The changed files were guaranteed to 211 // have been in the cached file list (because there was a strong 212 // reference from changedFiles. 213 if (changedFiles != null) { 214 Iterator<MacOSXPreferencesFile> iterChanged = changedFiles.iterator(); 215 while (iterChanged.hasNext()) { 216 MacOSXPreferencesFile f = iterChanged.next(); 217 if (f != null && f.user == cfCurrentUser) 218 iterChanged.remove(); 219 } 220 } 221 return ok; 222 } 223 224 225 226 227 // Write all prefs changes to disk, but do not clear all cached prefs 228 // values. Also kills any scheduled flush task. 229 // There's no CFPreferencesFlush() (<rdar://problem/3049129>), so lots of cached prefs 230 // are cleared anyway. 231 static synchronized boolean flushWorld() 232 { 233 boolean ok = true; 234 235 if (changedFiles != null && !changedFiles.isEmpty()) { 236 Iterator iter = changedFiles.iterator(); 237 while (iter.hasNext()) { 238 MacOSXPreferencesFile f = (MacOSXPreferencesFile)iter.next(); 239 if (!f.synchronize()) ok = false; 240 } 241 242 changedFiles.clear(); 243 } 244 245 if (flushTimerTask != null) { 246 flushTimerTask.cancel(); 247 flushTimerTask = null; 248 } 249 250 return ok; 251 } 252 253 // Mark this prefs file as changed. The changes will be flushed in 254 // at most flushDelay() seconds. 255 // Must be called when synchronized on MacOSXPreferencesFile.class 256 private void markChanged() 257 { 258 // Add this file to the changed file list 259 if (changedFiles == null) changedFiles = new HashSet(); 260 changedFiles.add(this); 261 262 // Schedule a new flush and a shutdown hook, if necessary 263 if (flushTimerTask == null) { 264 flushTimerTask = new FlushTask(); 265 timer().schedule(flushTimerTask, flushDelay() * 1000); 266 } 267 } 268 269 // Return the flush delay, initializing from a property if necessary. 270 private static synchronized long flushDelay() 271 { 272 if (flushDelay == -1) { 273 try { 274 // flush delay >= 5, default 60 275 flushDelay = Math.max(5, Integer.parseInt(System.getProperty("java.util.prefs.flushDelay", "60"))); 276 } catch (NumberFormatException e) { 277 flushDelay = 60; 278 } 279 } 280 return flushDelay; 281 } 282 283 // Initialize and run the sync timer, if the sync timer property is set 284 // and the sync timer hasn't already been started. 285 private static synchronized void initSyncTimerIfNeeded() 286 { 287 // syncInterval: -1 is uninitialized, other negative is off, 288 // positive is seconds between syncs (min 5). 289 290 if (syncInterval == -1) { 291 try { 292 syncInterval = Integer.parseInt(System.getProperty("java.util.prefs.syncInterval", "-2")); 293 if (syncInterval >= 0) { 294 // minimum of 5 seconds 295 syncInterval = Math.max(5, syncInterval); 296 } else { 297 syncInterval = -2; // default off 298 } 299 } catch (NumberFormatException e) { 300 syncInterval = -2; // bad property value - default off 301 } 302 303 if (syncInterval > 0) { 304 timer().schedule(new TimerTask() { 305 public void run() { MacOSXPreferencesFile.syncWorld();} 306 }, syncInterval * 1000, syncInterval * 1000); 307 } else { 308 // syncInterval property not set. No sync timer ever. 309 } 310 } 311 } 312 313 // Return the timer used for flush and sync, creating it if necessary. 314 private static synchronized Timer timer() 315 { 316 if (timer == null) { 317 timer = new Timer(true); // daemon 318 Thread flushThread = new Thread() { 319 public void run() { 320 flushWorld(); 321 } 322 }; 323 /* Set context class loader to null in order to avoid 324 * keeping a strong reference to an application classloader. 325 */ 326 flushThread.setContextClassLoader(null); 327 Runtime.getRuntime().addShutdownHook(flushThread); 328 } 329 return timer; 330 } 331 332 333 // Node manipulation 334 boolean addNode(String path) 335 { 336 synchronized(MacOSXPreferencesFile.class) { 337 markChanged(); 338 return addNode(path, appName, user, host); 339 } 340 } 341 342 void removeNode(String path) 343 { 344 synchronized(MacOSXPreferencesFile.class) { 345 markChanged(); 346 removeNode(path, appName, user, host); 347 } 348 } 349 350 void addChildToNode(String path, String child) 351 { 352 synchronized(MacOSXPreferencesFile.class) { 353 markChanged(); 354 addChildToNode(path, child+"/", appName, user, host); 355 } 356 } 357 358 void removeChildFromNode(String path, String child) 359 { 360 synchronized(MacOSXPreferencesFile.class) { 361 markChanged(); 362 removeChildFromNode(path, child+"/", appName, user, host); 363 } 364 } 365 366 367 // Key manipulation 368 void addKeyToNode(String path, String key, String value) 369 { 370 synchronized(MacOSXPreferencesFile.class) { 371 markChanged(); 372 addKeyToNode(path, key, value, appName, user, host); 373 } 374 } 375 376 void removeKeyFromNode(String path, String key) 377 { 378 synchronized(MacOSXPreferencesFile.class) { 379 markChanged(); 380 removeKeyFromNode(path, key, appName, user, host); 381 } 382 } 383 384 String getKeyFromNode(String path, String key) 385 { 386 synchronized(MacOSXPreferencesFile.class) { 387 return getKeyFromNode(path, key, appName, user, host); 388 } 389 } 390 391 392 // Enumerators 393 String[] getChildrenForNode(String path) 394 { 395 synchronized(MacOSXPreferencesFile.class) { 396 return getChildrenForNode(path, appName, user, host); 397 } 398 } 399 400 String[] getKeysForNode(String path) 401 { 402 synchronized(MacOSXPreferencesFile.class) { 403 return getKeysForNode(path, appName, user, host); 404 } 405 } 406 407 408 // Synchronization 409 boolean synchronize() 410 { 411 synchronized(MacOSXPreferencesFile.class) { 412 return synchronize(appName, user, host); 413 } 414 } 415 416 417 // CF functions 418 // Must be called when synchronized on MacOSXPreferencesFile.class 419 private static final native boolean 420 addNode(String path, String name, long user, long host); 421 private static final native void 422 removeNode(String path, String name, long user, long host); 423 private static final native void 424 addChildToNode(String path, String child, 425 String name, long user, long host); 426 private static final native void 427 removeChildFromNode(String path, String child, 428 String name, long user, long host); 429 private static final native void 430 addKeyToNode(String path, String key, String value, 431 String name, long user, long host); 432 private static final native void 433 removeKeyFromNode(String path, String key, 434 String name, long user, long host); 435 private static final native String 436 getKeyFromNode(String path, String key, 437 String name, long user, long host); 438 private static final native String[] 439 getChildrenForNode(String path, String name, long user, long host); 440 private static final native String[] 441 getKeysForNode(String path, String name, long user, long host); 442 private static final native boolean 443 synchronize(String name, long user, long host); 444 445 // CFPreferences host and user values (CFStringRefs) 446 private static long cfCurrentUser = currentUser(); 447 private static long cfAnyUser = anyUser(); 448 private static long cfCurrentHost = currentHost(); 449 private static long cfAnyHost = anyHost(); 450 451 // CFPreferences constant accessors 452 private static final native long currentUser(); 453 private static final native long anyUser(); 454 private static final native long currentHost(); 455 private static final native long anyHost(); 456 } 457