Source for gnu.java.util.prefs.GConfBasedPreferences

   1: /* GConfBasedPreferences.java -- GConf based Preferences implementation
   2:  Copyright (C) 2006 Free Software Foundation, Inc.
   3: 
   4:  This file is part of GNU Classpath.
   5: 
   6:  GNU Classpath is free software; you can redistribute it and/or modify
   7:  it under the terms of the GNU General Public License as published by
   8:  the Free Software Foundation; either version 2, or (at your option)
   9:  any later version.
  10: 
  11:  GNU Classpath is distributed in the hope that it will be useful, but
  12:  WITHOUT ANY WARRANTY; without even the implied warranty of
  13:  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14:  General Public License for more details.
  15: 
  16:  You should have received a copy of the GNU General Public License
  17:  along with GNU Classpath; see the file COPYING.  If not, write to the
  18:  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19:  02110-1301 USA.
  20: 
  21:  Linking this library statically or dynamically with other modules is
  22:  making a combined work based on this library.  Thus, the terms and
  23:  conditions of the GNU General Public License cover the whole
  24:  combination.
  25: 
  26:  As a special exception, the copyright holders of this library give you
  27:  permission to link this library with independent modules to produce an
  28:  executable, regardless of the license terms of these independent
  29:  modules, and to copy and distribute the resulting executable under
  30:  terms of your choice, provided that you also meet, for each linked
  31:  independent module, the terms and conditions of the license of that
  32:  module.  An independent module is a module which is not derived from
  33:  or based on this library.  If you modify this library, you may extend
  34:  this exception to your version of the library, but you are not
  35:  obligated to do so.  If you do not wish to do so, delete this
  36:  exception statement from your version. */
  37: 
  38: package gnu.java.util.prefs;
  39: 
  40: import gnu.java.util.prefs.gconf.GConfNativePeer;
  41: 
  42: import java.security.Permission;
  43: 
  44: import java.util.List;
  45: import java.util.prefs.AbstractPreferences;
  46: import java.util.prefs.BackingStoreException;
  47: 
  48: /**
  49:  * This is a GConf based preference implementation which writes the preferences
  50:  * as GConf key-value pairs. System Root is defined to be the
  51:  * <code>"/system"</code> directory of GConf for the current user, while User
  52:  * Root is <code>"/apps/java"</code>. These defaults can be modified by
  53:  * defining two system properties:<br />
  54:  * <br />
  55:  * User Root:<br />
  56:  * <br />
  57:  * 
  58:  * <pre>
  59:  * gnu.java.util.prefs.gconf.user_root
  60:  * </pre>
  61:  * 
  62:  * <br />
  63:  * <br />
  64:  * and System Root:<br />
  65:  * <br />
  66:  * 
  67:  * <pre>
  68:  * gnu.java.util.prefs.gconf.system_root
  69:  * </pre>
  70:  * 
  71:  * <br />
  72:  * 
  73:  * @author Mario Torre <neugens@limasoftware.net>
  74:  */
  75: public class GConfBasedPreferences
  76:     extends AbstractPreferences
  77: {
  78:   /** Get access to Runtime permission */
  79:   private static final Permission PERMISSION
  80:     = new RuntimePermission("preferences");
  81: 
  82:   /** CGonf client backend */
  83:   private static GConfNativePeer backend = new GConfNativePeer();
  84: 
  85:   /** Default user root path */
  86:   private static final String DEFAULT_USER_ROOT = "/apps/classpath";
  87: 
  88:   /** Default system root path */
  89:   private static final String DEFAULT_SYSTEM_ROOT = "/system";
  90: 
  91:   /** current node full path */
  92:   private String node = "";
  93: 
  94:   /** True if this is a preference node in the user tree, false otherwise. */
  95:   private final boolean isUser;
  96: 
  97:   /**
  98:    * Creates a preference root user node.
  99:    */
 100:   public GConfBasedPreferences()
 101:   {
 102:     this(true);
 103:   }
 104: 
 105:   /**
 106:    * Creates a preference root node. When <code>isUser</code> is true it will
 107:    * be user node otherwise it will be a system node.
 108:    */
 109:   public GConfBasedPreferences(boolean isUser)
 110:   {
 111:     this(null, "", isUser);
 112:   }
 113: 
 114:   /**
 115:    * Creates a new preference node given a parent node and a name, which has to
 116:    * be relative to its parent. When <code>isUser</code> is true it will be user
 117:    * node otherwise it will be a system node.
 118:    * 
 119:    * @param parent The parent node of this newly created node.
 120:    * @param name A name relative to the parent node.
 121:    * @param isUser Set to <code>true</code> initializes this node to be
 122:    * a user node, <code>false</code> initialize it to be a system node.
 123:    */
 124:   public GConfBasedPreferences(AbstractPreferences parent, String name,
 125:                                boolean isUser)
 126:   {
 127:     super(parent, name);
 128:     this.isUser = isUser;
 129: 
 130:     // stores the fully qualified name of this node
 131:     String absolutePath = this.absolutePath();
 132:     if (absolutePath != null && absolutePath.endsWith("/"))
 133:       {
 134:         absolutePath = absolutePath.substring(0, absolutePath.length() - 1);
 135:       }
 136: 
 137:     // strip invalid characters
 138:     // please, note that all names are unescaped into the native peer
 139:     int index = absolutePath.lastIndexOf('/');
 140:     if (index > -1)
 141:       {
 142:         absolutePath = absolutePath.substring(0, index + 1);
 143:         absolutePath = absolutePath + GConfNativePeer.escapeString(name);
 144:       }
 145:     
 146:     this.node = this.getRealRoot(isUser) + absolutePath;
 147: 
 148:     boolean nodeExist = backend.nodeExist(this.node);
 149: 
 150:     this.newNode = !nodeExist;
 151:   }
 152: 
 153:   /**
 154:    * Returns a child node with the given name.
 155:    * If the child node does not exists, it will be created.
 156:    * 
 157:    * @param name The name of the requested node.
 158:    * @return A new reference to the node, creating the node if it is necessary.
 159:    */
 160:   protected AbstractPreferences childSpi(String name)
 161:   {
 162:     // we don't check anything here, if the node is a new node this will be
 163:     // detected in the constructor, so we simply return a new reference to
 164:     // the requested node.
 165:     
 166:     GConfBasedPreferences preferenceNode
 167:       = new GConfBasedPreferences(this, name, this.isUser);
 168:     
 169:     // register the node for to GConf so that it can listen
 170:     // events outside the scope of the application
 171:     backend.startWatchingNode(this.node);
 172:     
 173:     return preferenceNode;
 174:   }
 175: 
 176:   /**
 177:    * Returns an array of names of the children of this preference node.
 178:    * If the current node does not have children, the returned array will be
 179:    * of <code>size</code> 0 (that is, not <code>null</code>).
 180:    * 
 181:    * @return A <code>String</code> array of names of children of the current
 182:    * node.
 183:    * @throws BackingStoreException if this operation cannot be completed.
 184:    */
 185:   protected String[] childrenNamesSpi() throws BackingStoreException
 186:   {
 187:     List<String> nodeList = backend.getChildrenNodes(this.node);
 188:     String[] nodes = new String[nodeList.size()];
 189:     nodeList.toArray(nodes);
 190: 
 191:     return nodes;
 192:   }
 193: 
 194:   /**
 195:    * Suggest a flush to the backend. Actually, this is only a suggestion as
 196:    * GConf handles this for us asynchronously. More over, both sync and flush
 197:    * have the same meaning in this class, so calling sync has exactly the same
 198:    * effect.
 199:    * 
 200:    * @see #sync
 201:    * @throws BackingStoreException if this operation cannot be completed.
 202:    */
 203:   public void flush() throws BackingStoreException
 204:   {
 205:     backend.suggestSync();
 206:   }
 207: 
 208:   /**
 209:    * Request a flush.
 210:    * 
 211:    * @see #flush
 212:    * @throws BackingStoreException if this operation cannot be completed.
 213:    */
 214:   protected void flushSpi() throws BackingStoreException
 215:   {
 216:     this.flush();
 217:   }
 218: 
 219:   /**
 220:    * Returns all of the key in this preference node.
 221:    * If the current node does not have preferences, the returned array will be
 222:    * of size zero.
 223:    * 
 224:    * @return A <code>String</code> array of keys stored under the current
 225:    * node.
 226:    * @throws BackingStoreException if this operation cannot be completed.
 227:    */
 228:   protected String[] keysSpi() throws BackingStoreException
 229:   {
 230:     List<String> keyList = backend.getKeys(this.node);
 231:     String[] keys = new String[keyList.size()];
 232:     keyList.toArray(keys);
 233: 
 234:     return keys;
 235:   }
 236: 
 237:   /**
 238:    * Does a recursive postorder traversal of the preference tree, starting from
 239:    * the given directory invalidating every preference found in the node.
 240:    * 
 241:    * @param directory The name of the starting directory (node)
 242:    */
 243:   private void postorderRemove(String directory)
 244:   {
 245:     try
 246:       {
 247:         // gets the listing of directories in this node
 248:         List<String> dirs = backend.getChildrenNodes(directory);
 249: 
 250:         if (dirs.size() != 0)
 251:           {
 252:             for (String currentDir : dirs)
 253:               {
 254:                 // recursive search inside this directory
 255:                 postorderRemove(currentDir);
 256:               }
 257:           }
 258: 
 259:         // remove all the keys associated to this directory
 260:         List<String> entries = backend.getKeys(directory);
 261: 
 262:         if (entries.size() != 0)
 263:           {
 264:             for (String key : entries)
 265:               {
 266:                 this.removeSpi(key);
 267:               }
 268:           }
 269:       }
 270:     catch (BackingStoreException ex)
 271:       {
 272:         /* ignore */
 273:       }
 274:   }
 275: 
 276:   /**
 277:    * Stores the given key-value pair into this preference node.
 278:    * 
 279:    * @param key The key of this preference.
 280:    * @param value The value of this preference.
 281:    */
 282:   protected void putSpi(String key, String value)
 283:   {
 284:     backend.setString(this.getGConfKey(key), value);
 285:   }
 286: 
 287:   /**
 288:    * Removes this preference node, including all its children.
 289:    * Also removes the preferences associated.
 290:    */
 291:   protected void removeNodeSpi() throws BackingStoreException
 292:   {
 293:     this.postorderRemove(this.node);
 294:     this.flush();
 295:   }
 296: 
 297:   /**
 298:    * Removes the given key from this preference node.
 299:    * If the key does not exist, no operation is performed.
 300:    * 
 301:    * @param key The key to remove.
 302:    */
 303:   protected void removeSpi(String key)
 304:   {
 305:     backend.unset(this.getGConfKey(key));
 306:   }
 307: 
 308:   /**
 309:    * Suggest a sync to the backend. Actually, this is only a suggestion as GConf
 310:    * handles this for us asynchronously. More over, both sync and flush have the
 311:    * same meaning in this class, so calling flush has exactly the same effect.
 312:    * 
 313:    * @see #flush
 314:    * @throws BackingStoreException if this operation cannot be completed due to
 315:    *           a failure in the backing store, or inability to communicate with
 316:    *           it.
 317:    */
 318:   public void sync() throws BackingStoreException
 319:   {
 320:     this.flush();
 321:   }
 322: 
 323:   /**
 324:    * Request a sync.
 325:    * 
 326:    * @see #sync
 327:    * @throws BackingStoreException if this operation cannot be completed due to
 328:    *           a failure in the backing store, or inability to communicate with
 329:    *           it.
 330:    */
 331:   protected void syncSpi() throws BackingStoreException
 332:   {
 333:     this.sync();
 334:   }
 335: 
 336:   /**
 337:    * Returns the value of the given key.
 338:    * If the keys does not have a value, or there is an error in the backing
 339:    * store, <code>null</code> is returned instead.
 340:    * 
 341:    * @param key The key to retrieve.
 342:    * @return The value associated with the given key.
 343:    */
 344:   protected String getSpi(String key)
 345:   {
 346:     return backend.getKey(this.getGConfKey(key));
 347:   }
 348: 
 349:   /**
 350:    * Returns <code>true</code> if this preference node is a user node,
 351:    * <code>false</code> if is a system preference node.
 352:    * 
 353:    * @return <code>true</code> if this preference node is a user node,
 354:    * <code>false</code> if is a system preference node.
 355:    */
 356:   public boolean isUserNode()
 357:   {
 358:     return this.isUser;
 359:   }
 360: 
 361:   /*
 362:    * PRIVATE METHODS
 363:    */
 364: 
 365:   /**
 366:    * Builds a GConf key string suitable for operations on the backend.
 367:    * 
 368:    * @param key The key to convert into a valid GConf key.
 369:    * @return A valid Gconf key.
 370:    */
 371:   private String getGConfKey(String key)
 372:   {
 373:     String nodeName = "";
 374:     
 375:     // strip key
 376:     // please, note that all names are unescaped into the native peer
 377:     key = GConfNativePeer.escapeString(key);
 378:     
 379:     if (this.node.endsWith("/"))
 380:       {
 381:         nodeName = this.node + key;
 382:       }
 383:     else
 384:       {
 385:         nodeName = this.node + "/" + key;
 386:       }
 387:     
 388:     return nodeName;
 389:   }
 390: 
 391:   /**
 392:    * Builds the root node to use for this preference.
 393:    * 
 394:    * @param isUser Defines if this node is a user (<code>true</code>) or system
 395:    * (<code>false</code>) node.
 396:    * @return The real root of this preference tree.
 397:    */
 398:   private String getRealRoot(boolean isUser)
 399:   {
 400:     // not sure about this, we should have already these permissions...
 401:     SecurityManager security = System.getSecurityManager();
 402: 
 403:     if (security != null)
 404:       {
 405:         security.checkPermission(PERMISSION);
 406:       }
 407: 
 408:     String root = null;
 409: 
 410:     if (isUser)
 411:       {
 412:         root = System.getProperty("gnu.java.util.prefs.gconf.user_root",
 413:                                   DEFAULT_USER_ROOT);
 414:       }
 415:     else
 416:       {
 417:         root = System.getProperty("gnu.java.util.prefs.gconf.system_root",
 418:                                   DEFAULT_SYSTEM_ROOT);
 419:       }
 420: 
 421:     return root;
 422:   }
 423: }