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()) { 205 ok = false; 206 } 207 } else { 208 iter.remove(); 209 } 210 } 211 } 212 // Renove synchronized file from changed file list. The changed files were 213 // guaranteed to have been in the cached file list (because there was a strong 214 // reference from changedFiles. 215 if (changedFiles != null) { 216 Iterator<MacOSXPreferencesFile> iterChanged = changedFiles.iterator(); 217 while (iterChanged.hasNext()) { 218 MacOSXPreferencesFile f = iterChanged.next(); 219 if (f != null && f.user == cfCurrentUser) 220 iterChanged.remove(); 221 } 222 } 223 return ok; 224 } 225 226 227 228 229 // Write all prefs changes to disk, but do not clear all cached prefs 230 // values. Also kills any scheduled flush task. 231 // There's no CFPreferencesFlush() (<rdar://problem/3049129>), so lots of cached prefs 232 // are cleared anyway. 233 static synchronized boolean flushWorld() 234 { 235 boolean ok = true; 236 237 if (changedFiles != null && !changedFiles.isEmpty()) { 238 Iterator iter = changedFiles.iterator(); 239 while (iter.hasNext()) { 240 MacOSXPreferencesFile f = (MacOSXPreferencesFile)iter.next(); 241 if (!f.synchronize()) ok = false; 242 } 243 244 changedFiles.clear(); 245 } 246 247 if (flushTimerTask != null) { 248 flushTimerTask.cancel(); 249 flushTimerTask = null; 250 } 251 252 return ok; 253 } 254 255 // Mark this prefs file as changed. The changes will be flushed in 256 // at most flushDelay() seconds. 257 // Must be called when synchronized on MacOSXPreferencesFile.class 258 private void markChanged() 259 { 260 // Add this file to the changed file list 261 if (changedFiles == null) changedFiles = new HashSet(); 262 changedFiles.add(this); 263 264 // Schedule a new flush and a shutdown hook, if necessary 265 if (flushTimerTask == null) { 266 flushTimerTask = new FlushTask(); 267 timer().schedule(flushTimerTask, flushDelay() * 1000); 268 } 269 } 270 271 // Return the flush delay, initializing from a property if necessary. 272 private static synchronized long flushDelay() 273 { 274 if (flushDelay == -1) { 275 try { 276 // flush delay >= 5, default 60 277 flushDelay = Math.max(5, Integer.parseInt(System.getProperty("java.util.prefs.flushDelay", "60"))); 278 } catch (NumberFormatException e) { 279 flushDelay = 60; 280 } 281 } 282 return flushDelay; 283 } 284 285 // Initialize and run the sync timer, if the sync timer property is set 286 // and the sync timer hasn't already been started. 287 private static synchronized void initSyncTimerIfNeeded() 288 { 289 // syncInterval: -1 is uninitialized, other negative is off, 290 // positive is seconds between syncs (min 5). 291 292 if (syncInterval == -1) { 293 try { 294 syncInterval = Integer.parseInt(System.getProperty("java.util.prefs.syncInterval", "-2")); 295 if (syncInterval >= 0) { 296 // minimum of 5 seconds 297 syncInterval = Math.max(5, syncInterval); 298 } else { 299 syncInterval = -2; // default off 300 } 301 } catch (NumberFormatException e) { 302 syncInterval = -2; // bad property value - default off 303 } 304 305 if (syncInterval > 0) { 306 timer().schedule(new TimerTask() { 307 public void run() { MacOSXPreferencesFile.syncWorld();} 308 }, syncInterval * 1000, syncInterval * 1000); 309 } else { 310 // syncInterval property not set. No sync timer ever. 311 } 312 } 313 } 314 315 // Return the timer used for flush and sync, creating it if necessary. 316 private static synchronized Timer timer() 317 { 318 if (timer == null) { 319 timer = new Timer(true); // daemon 320 Thread flushThread = new Thread() { 321 public void run() { 322 flushWorld(); 323 } 324 }; 325 /* Set context class loader to null in order to avoid 326 * keeping a strong reference to an application classloader. 327 */ 328 flushThread.setContextClassLoader(null); 329 Runtime.getRuntime().addShutdownHook(flushThread); 330 } 331 return timer; 332 } 333 334 335 // Node manipulation 336 boolean addNode(String path) 337 { 338 synchronized(MacOSXPreferencesFile.class) { 339 markChanged(); 340 return addNode(path, appName, user, host); 341 } 342 } 343 344 void removeNode(String path) 345 { 346 synchronized(MacOSXPreferencesFile.class) { 347 markChanged(); 348 removeNode(path, appName, user, host); 349 } 350 } 351 352 void addChildToNode(String path, String child) 353 { 354 synchronized(MacOSXPreferencesFile.class) { 355 markChanged(); 356 addChildToNode(path, child+"/", appName, user, host); 357 } 358 } 359 360 void removeChildFromNode(String path, String child) 361 { 362 synchronized(MacOSXPreferencesFile.class) { 363 markChanged(); 364 removeChildFromNode(path, child+"/", appName, user, host); 365 } 366 } 367 368 369 // Key manipulation 370 void addKeyToNode(String path, String key, String value) 371 { 372 synchronized(MacOSXPreferencesFile.class) { 373 markChanged(); 374 addKeyToNode(path, key, value, appName, user, host); 375 } 376 } 377 378 void removeKeyFromNode(String path, String key) 379 { 380 synchronized(MacOSXPreferencesFile.class) { 381 markChanged(); 382 removeKeyFromNode(path, key, appName, user, host); 383 } 384 } 385 386 String getKeyFromNode(String path, String key) 387 { 388 synchronized(MacOSXPreferencesFile.class) { 389 return getKeyFromNode(path, key, appName, user, host); 390 } 391 } 392 393 394 // Enumerators 395 String[] getChildrenForNode(String path) 396 { 397 synchronized(MacOSXPreferencesFile.class) { 398 return getChildrenForNode(path, appName, user, host); 399 } 400 } 401 402 String[] getKeysForNode(String path) 403 { 404 synchronized(MacOSXPreferencesFile.class) { 405 return getKeysForNode(path, appName, user, host); 406 } 407 } 408 409 410 // Synchronization 411 boolean synchronize() 412 { 413 synchronized(MacOSXPreferencesFile.class) { 414 return synchronize(appName, user, host); 415 } 416 } 417 418 419 // CF functions 420 // Must be called when synchronized on MacOSXPreferencesFile.class 421 private static final native boolean 422 addNode(String path, String name, long user, long host); 423 private static final native void 424 removeNode(String path, String name, long user, long host); 425 private static final native void 426 addChildToNode(String path, String child, 427 String name, long user, long host); 428 private static final native void 429 removeChildFromNode(String path, String child, 430 String name, long user, long host); 431 private static final native void 432 addKeyToNode(String path, String key, String value, 433 String name, long user, long host); 434 private static final native void 435 removeKeyFromNode(String path, String key, 436 String name, long user, long host); 437 private static final native String 438 getKeyFromNode(String path, String key, 439 String name, long user, long host); 440 private static final native String[] 441 getChildrenForNode(String path, String name, long user, long host); 442 private static final native String[] 443 getKeysForNode(String path, String name, long user, long host); 444 private static final native boolean 445 synchronize(String name, long user, long host); 446 447 // CFPreferences host and user values (CFStringRefs) 448 private static long cfCurrentUser = currentUser(); 449 private static long cfAnyUser = anyUser(); 450 private static long cfCurrentHost = currentHost(); 451 private static long cfAnyHost = anyHost(); 452 453 // CFPreferences constant accessors 454 private static final native long currentUser(); 455 private static final native long anyUser(); 456 private static final native long currentHost(); 457 private static final native long anyHost(); 458 } 459