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