The Artima Developer Community
Sponsored Link

Java Buzz Forum
My web.xml file is finally empty!

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic    
Flat View: This topic has 0 replies on 1 page
Mats Henricson

Posts: 55
Nickname: matsh
Registered: May, 2003

Mats Henricson is interested in too much
My web.xml file is finally empty! Posted: Jun 26, 2003 1:48 AM
Reply to this message Reply

This post originated from an RSS feed registered with Java Buzz by Mats Henricson.
Original Post: My web.xml file is finally empty!
Feed Title: Code:Q
Feed URL: http://sedoparking.com/search/registrar.php?domain=®istrar=sedopark
Feed Description: Mats Henricson's weblog
Latest Java Buzz Posts
Latest Java Buzz Posts by Mats Henricson
Latest Posts From Code:Q

Advertisement


Or, how I escaped preferences hell.

System preferences is an area that I've seen badly managed in several projects. Nobody cares to specify where they should be stored, or how they should be accessed, so they end up in web.xml files, other preference specific files, the database, or as command line options when the system is started. It can easily become a real mess.

For my packing list I saw my web.xml file grow to be a real mess, and decided to make an optimal solution. My requirements were:
  • My application is rather small, so there was no need to partition preferences in nodes for specific portions of the application, as is possible with java.util.prefs.
  • There is currently no need for user preferences, just system preferences.
  • I wanted to use an XML file to store preferences. I ended up calling my file prefs.xml
  • I wanted preferences to be identified by string keys, such as "hibernate.connection.url".
  • I wanted all preferences to be strongly typed. Right now I support String, int and boolean. It is very simple for me to add new types, but right now I don't need it.
  • I did not want to depend on default values, since I think that is a bad bad mistake in the design of java.util.prefs.Preferences. Instead I wanted an exception to be thrown if the value of a preference could not be found.
  • I wanted to have the simplest possible way for my application to get to preference values, and the simplest way I could figure out was a Prefs class with static methods getString(), getInt() and getBoolean(), all taking a string key as parameter.
  • I have at least two deployment machines, one at home where I develop and test, and one where the application is hosted. I wanted a seamless way to handle the preference differences between these machines, plus a simple way to add a new machine, if necessary.
  • I wanted all of my application, except the Prefs class, to be totally unknowing of the machine where it was deployed. It turned out that in the end I could make even the Prefs class unknowing, so the only file where I will have to make changes is the prefs.xml file.
  • I wanted the preferences to be accessible anytime and from anywhere in my application. This meant that I could not depend on any servlet to be initialized in advance. The Prefs class had to be totally self sufficient.
  • I wanted to use JPreferences, which I bumped into on Sourceforge,and immediately liked.
So, how did I do this? There were a few problems that needed to be solved:
  • How did I make sure preferences were available when asked for?
  • How did the Prefs class find its prefs.xml file?
  • How did I make sure the application was using a certain set of preferences on my machine at home, and another set on the hosting server?
  • How did I make sure an exception was thrown if a preference for a certain key wasn't found?
The loading of preferences seems simple enough, since a static initializer seemed like the natural solution. After all, no function in the Prefs class can be used before its static init block is executed. However, that turned out to not work. I used the Prefs class in a JSP, and got thrown a nasty error in my face about initilization, if I remember correctly. So, the solution to this problem ended up being late initialization. However, that usually sucks in static methods, since you end up calling an init() function at the very beginning of each get*() function, just to make sure the prefs.xml file is loaded. This led me to use a new idiom I've never used before: a singleton where even the getInstance() function is private, and called from inside the public static get*() functions. This was my first attempt:

    public static boolean getBoolean(String key)
    {
        return Prefs.getInstance().getBoolean(key);
    }
But this doesn't work, since you can't have two functions with the same signature, with the only difference that one is static. So, I ended up having to add "Impl" as suffix:

    public boolean getBoolean(String key)
    {
        return Prefs.getInstance().getBooleanImpl(key);
    }
No big deal, I think, since the "Impl" functions are private. Now, as you may have guessed, the getInstance() function calls the constructor in the normal singleton way, which in turn loads the prefs.xml file. So, how does it find it in the file system? The application might be running on a Linux or Windows box! A thread in a Java blog pointed me to the best way to do it, even though it doesn't work if called from JUnit. Here is the relevant part of the constructor:

    private Prefs()
    {
        // ...
        String fullPath = Prefs.class.getProtectionDomain().getCodeSource().getLocation().getFile();
        String classPart = Prefs.class.getName().replace('.', '/');
        int afterClassesIndex = fullPath.indexOf(classPart);
        String prefsFile = fullPath.substring(0, afterClassesIndex) + "prefs.xml";
        XMLStore store = XMLStore.createFromFile(prefsFile, null);
        // ...
    }
It looks rather contrieved, but works well, assuming the prefs.xml file is at the root of where the Prefs class was loaded. For me that is in the WEB-INF/classes directory.

Now, how do I make sure different preferences are used on different machines? This is a bit messy, since some preferences are actually the same in both cases, such as the name of the mySQL JDBC driver, while the mySQL JDBC URL is different (absolute in one case, and local in the other). The way I solved this was to have one root node in my prefs.xml file, and one sub node for each machine where it was deployed. I chose the name of these machine-dependent nodes to be the name of the machines. Here's a snippet from my prefs.xml file:
  <preferences EXTERNAL_XML_VERSION="1.0">
    <root type="system">
      <node name="/">                <!-- THESE PREFRENCES ARE THE SAME ALL THE TIME -->
        <map>
          <entry key="hibernate.connection.driver_class" value="org.gjt.mm.mysql.Driver"/>
          <entry key="some.key" value="true"/>
        </map>
      </node>
      <node name="home">             <!-- THESE ARE USED AT HOME, WHEN DEVELOPING -->
        <map>
          <entry key="hibernate.connection.url" value="jdbc:mysql://venus.webappcabaret.net/codeq"/>
          <entry key="some.other.key" value="777777"/>
        </map>
      </node>
      <node name="app.host.com">     <!-- THESE ARE USED AT DEPLOYMENT, AT app.host.com -->
        <map>
          <entry key="hibernate.connection.url" value="jdbc:mysql://mysql/myapp"/>
          <entry key="some.other.key" value="888888"/>
        </map>
      </node>
    </root>
  </preferences>
The reason for using the name of the machine as the name of the node is that I could use the return value from InetAddress.getLocalHost().getHostName() and pass it on to the node() function of the JPreferences Preferences class. Now, in which way is this smart? The thing is, I create one Preferences object for the root node, where common preferences are saved, and another Preferences object for the machine-dependedent preferences. Then, when for example the static Prefs.getInt(String key) function is called, I first check to see if there's an int prefs in the machine-dependent Preferences object for this key. If not, then I also look it up in the root node. If it isn't in either of these nodes, I throw an exception, since I rally dislike default values.

Now, how did I manage to throw exceptions when a preference was never found? The problem is with the design of the java.util.prefs.Preferences class, where a typical get function have this signature:

    public boolean getBoolean(String key, boolean def)
Now, if I call it like this, prefs.getBoolean("displayCounter", false) and get a false return value, then I don't know if that preference was set to false, or didn't exist. How did I solve this? The solution is an ugly trick. Just as with JDBC ResultSets, just about all data can be fetched with the getString() function. So, what I did was to first make a call getString("displayCounter", "xyzxyzxyzxyz"), and then check if I get that unique "xyzxyzxyzxyz" string back. If so, then I knew there was no preference for the "displayCounter", and I can throw an exception. If I get something else back (in this case the strings "true" or "false"), then I knew I could use the getBoolean("displayCounter", false) call, and know I get a real preference back. Not elegent, but works.

OK, so what are the drawbacks with this solution? First, performance may not be too good, since it is not cacheing the fetched preferences. But that is rather simple to fix. Also, I'm doing at most two checks to see if a preference is available before I actually get it. Performance doesn't bother me much (I get < 300 hits a day, and response time is about 200 ms), so right now I'm opting for the most elegant and simple solution. Another drawback is that the application can't save changes to preferences. Currently I have no need for it, so I'll pass for now. Finally, there is no way for an admin application to change preferences at runtime. I might add that as a feature in the future, but right now I'm really happy with the current code.

If you want the Prefs class and a sample prefs.xml file, just let me know. You may use it as you wish. Just put the Prefs class where you want it, change its package declaration, and edit the prefs.xml file the way you want it.

Read: My web.xml file is finally empty!

Topic: ctl+shift+v in IDEA Previous Topic    

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use