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.Iterator;
  45: import java.util.List;
  46: import java.util.prefs.AbstractPreferences;
  47: import java.util.prefs.BackingStoreException;
  48: 
  49: /**
  50:  * This is a GConf based preference implementation which writes the preferences
  51:  * as GConf key-value pairs. System Root is defined to be the
  52:  * <code>"/system"</code> directory of GConf for the current user, while User
  53:  * Root is <code>"/apps/java"</code>. These defaults can be modified by
  54:  * defining two system properties:<br />
  55:  * <br />
  56:  * User Root:<br />
  57:  * <br />
  58:  * 
  59:  * <pre>
  60:  * gnu.java.util.prefs.gconf.user_root
  61:  * </pre>
  62:  * 
  63:  * <br />
  64:  * <br />
  65:  * and System Root:<br />
  66:  * <br />
  67:  * 
  68:  * <pre>
  69:  * gnu.java.util.prefs.gconf.system_root
  70:  * </pre>
  71:  * 
  72:  * <br />
  73:  * 
  74:  * @author Mario Torre <neugens@limasoftware.net>
  75:  * @version 1.0.1
  76:  */
  77: public class GConfBasedPreferences
  78:     extends AbstractPreferences
  79: {
  80:   /** Get access to Runtime permission */
  81:   private static final Permission PERMISSION
  82:     = new RuntimePermission("preferences");
  83: 
  84:   /** CGonf client backend */
  85:   private static GConfNativePeer backend = new GConfNativePeer();
  86: 
  87:   /** Default user root path */
  88:   private static final String DEFAULT_USER_ROOT = "/apps/classpath";
  89: 
  90:   /** Default system root path */
  91:   private static final String DEFAULT_SYSTEM_ROOT = "/system";
  92: 
  93:   /** current node full path */
  94:   private String node = "";
  95: 
  96:   /** True if this is a preference node in the user tree, false otherwise. */
  97:   private final boolean isUser;
  98: 
  99:   /**
 100:    * Creates a preference root user node.
 101:    */
 102:   public GConfBasedPreferences()
 103:   {
 104:     this(true);
 105:   }
 106: 
 107:   /**
 108:    * Creates a preference root node. When <code>isUser</code> is true it will
 109:    * be user node otherwise it will be a system node.
 110:    */
 111:   public GConfBasedPreferences(boolean isUser)
 112:   {
 113:     this(null, "", isUser);
 114:   }
 115: 
 116:   /**
 117:    * Creates a new preference node given a parent node and a name, which has to
 118:    * be relative to its parent. When <code>isUser</code> is true it will be user
 119:    * node otherwise it will be a system node.
 120:    * 
 121:    * @param parent The parent node of this newly created node.
 122:    * @param name A name relative to the parent node.
 123:    * @param isUser Set to <code>true</code> initializes this node to be
 124:    * a user node, <code>false</code> initialize it to be a system node.
 125:    */
 126:   public GConfBasedPreferences(AbstractPreferences parent, String name,
 127:                                boolean isUser)
 128:   {
 129:     super(parent, name);
 130:     this.isUser = isUser;
 131: 
 132:     // stores the fully qualified name of this node
 133:     String absolutePath = this.absolutePath();
 134:     if (absolutePath != null && absolutePath.endsWith("/"))
 135:       {
 136:         absolutePath = absolutePath.substring(0, absolutePath.length() - 1);
 137:       }
 138: 
 139:     this.node = this.getRealRoot(isUser) + absolutePath;
 140: 
 141:     boolean nodeExist = backend.nodeExist(this.node);
 142: 
 143:     this.newNode = !nodeExist;
 144:     backend.startWatchingNode(this.node);
 145:   }
 146: 
 147:   /**
 148:    * Returns a child node with the given name.
 149:    * If the child node does not exists, it will be created.
 150:    * 
 151:    * @param name The name of the requested node.
 152:    * @return A new reference to the node, creating the node if it is necessary.
 153:    */
 154:   protected AbstractPreferences childSpi(String name)
 155:   {
 156:     // we don't check anything here, if the node is a new node this will be
 157:     // detected in the constructor, so we simply return a new reference to
 158:     // the requested node.
 159:     return new GConfBasedPreferences(this, name, this.isUser);
 160:   }
 161: 
 162:   /**
 163:    * Returns an array of names of the children of this preference node.
 164:    * If the current node does not have children, the returned array will be
 165:    * of <code>size</code> 0 (that is, not <code>null</code>).
 166:    * 
 167:    * @return A <code>String</code> array of names of children of the current
 168:    * node.
 169:    * @throws BackingStoreException if this operation cannot be completed.
 170:    */
 171:   protected String[] childrenNamesSpi() throws BackingStoreException
 172:   {
 173:     List nodeList = backend.getChildrenNodes(this.node);
 174:     String[] nodes = new String[nodeList.size()];
 175:     nodeList.toArray(nodes);
 176: 
 177:     return nodes;
 178:   }
 179: 
 180:   /**
 181:    * Suggest a flush to the backend. Actually, this is only a suggestion as
 182:    * GConf handles this for us asynchronously. More over, both sync and flush
 183:    * have the same meaning in this class, so calling sync has exactly the same
 184:    * effect.
 185:    * 
 186:    * @see #sync
 187:    * @throws BackingStoreException if this operation cannot be completed.
 188:    */
 189:   public void flush() throws BackingStoreException
 190:   {
 191:     backend.suggestSync();
 192:   }
 193: 
 194:   /**
 195:    * Request a flush.
 196:    * 
 197:    * @see #flush
 198:    * @throws BackingStoreException if this operation cannot be completed.
 199:    */
 200:   protected void flushSpi() throws BackingStoreException
 201:   {
 202:     this.flush();
 203:   }
 204: 
 205:   /**
 206:    * Returns all of the key in this preference node.
 207:    * If the current node does not have preferences, the returned array will be
 208:    * of size zero.
 209:    * 
 210:    * @return A <code>String</code> array of keys stored under the current
 211:    * node.
 212:    * @throws BackingStoreException if this operation cannot be completed.
 213:    */
 214:   protected String[] keysSpi() throws BackingStoreException
 215:   {
 216:     List keyList = backend.getKeys(this.node);
 217:     String[] keys = new String[keyList.size()];
 218:     keyList.toArray(keys);
 219: 
 220:     return keys;
 221:   }
 222: 
 223:   /**
 224:    * Does a recursive postorder traversal of the preference tree, starting from
 225:    * the given directory invalidating every preference found in the node.
 226:    * 
 227:    * @param directory The name of the starting directory (node)
 228:    */
 229:   private void postorderRemove(String directory)
 230:   {
 231:     try
 232:       {
 233:         // gets the listing of directories in this node
 234:         List dirs = backend.getChildrenNodes(directory);
 235: 
 236:         if (dirs.size() != 0)
 237:           {
 238:             String currentDir = null;
 239: 
 240:             for (Iterator itr = dirs.iterator(); itr.hasNext();)
 241:               {
 242:                 currentDir = (String) itr.next();
 243: 
 244:                 // recursive search inside this directory
 245:                 postorderRemove(currentDir);
 246:               }
 247:           }
 248: 
 249:         // remove all the keys associated to this directory
 250:         List entries = backend.getKeys(directory);
 251: 
 252:         if (entries.size() != 0)
 253:           {
 254:             String key = null;
 255: 
 256:             for (Iterator keys = entries.iterator(); keys.hasNext();)
 257:               {
 258:                 key = (String) keys.next();
 259:                 this.removeSpi(key);
 260:               }
 261:           }
 262:       }
 263:     catch (BackingStoreException ex)
 264:       {
 265:         /* ignore */
 266:       }
 267:   }
 268: 
 269:   /**
 270:    * Stores the given key-value pair into this preference node.
 271:    * 
 272:    * @param key The key of this preference.
 273:    * @param value The value of this preference.
 274:    */
 275:   protected void putSpi(String key, String value)
 276:   {
 277:     backend.setString(this.getGConfKey(key), value);
 278:   }
 279: 
 280:   /**
 281:    * Removes this preference node, including all its children.
 282:    * Also removes the preferences associated.
 283:    */
 284:   protected void removeNodeSpi() throws BackingStoreException
 285:   {
 286:     this.postorderRemove(this.node);
 287:     this.flush();
 288:   }
 289: 
 290:   /**
 291:    * Removes the given key from this preference node.
 292:    * If the key does not exist, no operation is performed.
 293:    * 
 294:    * @param key The key to remove.
 295:    */
 296:   protected void removeSpi(String key)
 297:   {
 298:     backend.unset(this.getGConfKey(key));
 299:   }
 300: 
 301:   /**
 302:    * Suggest a sync to the backend. Actually, this is only a suggestion as GConf
 303:    * handles this for us asynchronously. More over, both sync and flush have the
 304:    * same meaning in this class, so calling flush has exactly the same effect.
 305:    * 
 306:    * @see #flush
 307:    * @throws BackingStoreException if this operation cannot be completed due to
 308:    *           a failure in the backing store, or inability to communicate with
 309:    *           it.
 310:    */
 311:   public void sync() throws BackingStoreException
 312:   {
 313:     this.flush();
 314:   }
 315: 
 316:   /**
 317:    * Request a sync.
 318:    * 
 319:    * @see #sync
 320:    * @throws BackingStoreException if this operation cannot be completed due to
 321:    *           a failure in the backing store, or inability to communicate with
 322:    *           it.
 323:    */
 324:   protected void syncSpi() throws BackingStoreException
 325:   {
 326:     this.sync();
 327:   }
 328: 
 329:   /**
 330:    * Returns the value of the given key.
 331:    * If the keys does not have a value, or there is an error in the backing
 332:    * store, <code>null</code> is returned instead.
 333:    * 
 334:    * @param key The key to retrieve.
 335:    * @return The value associated with the given key.
 336:    */
 337:   protected String getSpi(String key)
 338:   {
 339:     return backend.getKey(this.getGConfKey(key));
 340:   }
 341: 
 342:   /**
 343:    * Returns <code>true</code> if this preference node is a user node,
 344:    * <code>false</code> if is a system preference node.
 345:    * 
 346:    * @return <code>true</code> if this preference node is a user node,
 347:    * <code>false</code> if is a system preference node.
 348:    */
 349:   public boolean isUserNode()
 350:   {
 351:     return this.isUser;
 352:   }
 353: 
 354:   /*
 355:    * PRIVATE METHODS
 356:    */
 357: 
 358:   /**
 359:    * Builds a GConf key string suitable for operations on the backend.
 360:    * 
 361:    * @param key The key to convert into a valid GConf key.
 362:    * @return A valid Gconf key.
 363:    */
 364:   private String getGConfKey(String key)
 365:   {
 366:     String nodeName = "";
 367:     
 368:     if (this.node.endsWith("/"))
 369:       {
 370:         nodeName = this.node + key;
 371:       }
 372:     else
 373:       {
 374:         nodeName = this.node + "/" + key;
 375:       }
 376: 
 377:     return nodeName;
 378:   }
 379: 
 380:   /**
 381:    * Builds the root node to use for this preference.
 382:    * 
 383:    * @param isUser Defines if this node is a user (<code>true</code>) or system
 384:    * (<code>false</code>) node.
 385:    * @return The real root of this preference tree.
 386:    */
 387:   private String getRealRoot(boolean isUser)
 388:   {
 389:     // not sure about this, we should have already these permissions...
 390:     SecurityManager security = System.getSecurityManager();
 391: 
 392:     if (security != null)
 393:       {
 394:         security.checkPermission(PERMISSION);
 395:       }
 396: 
 397:     String root = null;
 398: 
 399:     if (isUser)
 400:       {
 401:         root = System.getProperty("gnu.java.util.prefs.gconf.user_root",
 402:                                   DEFAULT_USER_ROOT);
 403:       }
 404:     else
 405:       {
 406:         root = System.getProperty("gnu.java.util.prefs.gconf.system_root",
 407:                                   DEFAULT_SYSTEM_ROOT);
 408:       }
 409: 
 410:     return root;
 411:   }
 412: }