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