Menu

Your First Repository Framework App

Daniel P. Dougherty

Continuing with our jdoe set of examples from earlier, we will now work through a simple example showing how to work within the Repository Framework. For this example, we'll assume you are working with the Ant build system. Here are the steps we will be working on:

  1. Get Started...
  2. Extend the RepositoryStorage Interface
  3. Implement the ReposConfiguration Interface
  4. Extend the RespositoryConnectivity Interface
  5. Extend the RepositoryStorageDOM Class
  6. Extend the RepositoryStorageXML Class
  7. Extend the ReposPrefs Class
  8. Build the Application
  9. Test it Out in Groovy (optional)
  10. Typical Follow-Ups
    1. Further Extend the RepositoryStorage Interface to Meet Your Needs
    2. Delegating to a TransferAgent
    3. Verifying Changes Work

For your convenience, the entire source tree for the HelloApp example discussed below can be downloaded here:


Get Started...

Recall the directory structure we had set up for our mythical developer John Doe to carry out our HelloSnifflib.java example. You should now add the additional directories described below starting with src/com/jdoe/myapp/ -- that will be where we will build our new application. We will assume our mythical application is to be named "HelloApp".

  • build/
  • build.xml
  • lib/
    • snifflib-1.7.jar (or latest version)
  • release/
  • src/
    • com/
      • jdoe/
        • demos/
          • HelloSnifflib.java
        • myapp/
          • database/
            • HelloAppConfig.java
            • HelloAppConnectivity.java
            • HelloAppDOM.java
            • HelloAppPrefs.java
            • HelloAppStorage.java
            • HelloAppXMLConnection.java
            • HelloAppXML.java

Extend the RepositoryStorage Interface

When developing an application, extending the RepositoryStorage interface can be done without implementing any new methods. The methods can be built-in later on as needed. Therefore, owing to its simplicity, this is usually the first step you'll carry out.


The first step in using the Repository Framework is to extend the RepositoryStorage Interface for your storage needs. For example, if you are writing a messaging application, then this interface should describe the methods needed to set and get the text of the message.

Now use your favorite text editor (e.g. jEdit,NEdit etc. ) to create and edit the file HelloAppStorage.java mentioned above.

package com.jdoe.myapp.database;

import com.mockturtlesolutions.snifflib.reposconfig.database.RepositoryStorage;

/**
Interface methods that must be implemented by all HelloApp storage classes.
*/
public interface HelloAppStorage extends RepositoryStorage
{

}



Notice that at this point we haven't specified any additional methods beyond those inherited from RepositoryStorage Interface. Of course, we can always add to this interface as the need arises! Since we are starting with a simple working example though, for now, we will leave this interface definition sparse.

Implement the ReposConfiguration Interface

Now we come to the second standard step in bringing an application within the Repository Framework.


Snifflib already comes with several classes which implement the ReposConfiguration interface so it's convenient to simply extend one of these pre-existing classes. Since we may want to allow for SQL type back-ends in the future, we will go ahead and extend the SQLConfig class.

package com.jdoe.myapp.database;

import java.io.File;    
import java.util.HashSet;
import java.util.LinkedHashMap;
import com.mockturtlesolutions.snifflib.sqldig.database.SQLConfig;

public class HelloAppConfig extends SQLConfig 
{

    public HelloAppConfig()
    {
        this.ConfigEnvironmentVariable="HELLOAPPCONFIG";
        this.UsrConfigFile=(String)System.getProperty("user.home").concat(File.separator).concat(".myhelloapprepos");
        this.SysConfigFile = System.getenv(this.ConfigEnvironmentVariable);
        this.setSplitString(",");
    }

    public Class supportedInterface()
    {
        return(HelloAppStorage.class);
    }

    public static Class getPreferencesClass()
    {
        Class out = HelloAppPrefs.class;
        return(out);
    }

    public HashSet getDomainNameFilterConfigs()
    {
        HashSet out = super.getDomainNameFilterConfigs();
        out.add("protocol");
        return(out);
    }

    public HashSet getFileChooserConfigs()
    {
        HashSet out = new HashSet();
        out.add("database");
        return(out);
    }

    public HashSet getYesNoConfigs()
    {
        HashSet out = new HashSet();
        out.add("enabled");
        return(out);
    }

    public boolean isEnabled(String repos)
    {
        boolean out = false;

        if (repos ==null) //The null repository (persistence mode) is always enabled.
        {
            out = true;
        }
        else
        {
            out = this.getConfigValue(repos,"enabled").equalsIgnoreCase("yes");
        }

        return(out);
    }

    public LinkedHashMap getDefaultConfig()
    {
        LinkedHashMap configmap = new LinkedHashMap();

        //Host user and password could be required to connect to an SQL database 
        //so include configurations for them here.
        configmap.put("host","");
        configmap.put("user","");
        configmap.put("password","");

        configmap.put("database",(String)System.getProperty("user.home").concat(File.separator).concat("helloAppDB"));
        configmap.put("timeout","300000");  
        configmap.put("enabled","yes");
        configmap.put("editor","");

        //Put the default protocol(s) here.  For now, we assume user will want to store stuff locally
        // on their machine using an XML back-end.

        configmap.put("protocol","com.mockturtlesolutions.snifflib.datatypes.database.HelloAppXMLConnection");

        return(configmap);
    }
}


While this may look intimidating at first, it it fairly boiler-plate stuff. The Config's code does do a number of nifty things. It sets up the default back-end as an XML database whose files will be located in the user's home directory under the folder helloAppDB/. It also allows the user to override the location of this folder (to some other folder) by setting the value of an environment variable. To find review how to set environment variables on various OS, click on the following links:

Furthermore, the output from methods like getYesNoConfigs() and getFileChooserConfigs(), will be used by graphical configuration tools to help format the values of configs in GUI. For example, a check box might be used to graphically configure a config value listed under getYesNoConfigs(), while a graphical file browser might be used to configure a config value listed under getFileChooserConfigs().

Extend the RespositoryConnectivity Interface

Usually only very minimal changes to the standard connectivity classes are needed to establish connections to your various back-ends. Owing to this step's simplicity, this is usually the next step you'll want to take.


Next for our HelloApp, we will extend the RespositoryConnectivity interface as in the following:

package com.jdoe.myapp.database;

import com.mockturtlesolutions.snifflib.reposconfig.database.RepositoryConnectivity;

public interface HelloAppConnectivity extends RepositoryConnectivity
{

}


This allows us to effectively mark all classes designed to manage connections to back-ends storing HelloApp instances. For now, nothing more needs to be said about this. Instead, let's get to finally defining some back-end storage classes!

Extend the RepositoryStorageDOM Class

Storage as a DOM is a great next step to take.


This code defines our first storage class. An instance of a DOM or Document Object Model is one way to hold and access a RepositoryStorage in memory at runtime. Unlike other RepositoryStorage back-ends it does not allow for long-term persistence since it only exists in memory while your program is running. The code given below simply extends the functionality already provided by Snifflib's RepositoryStorageDOM class.

package com.jdoe.myapp.database;

import com.mockturtlesolutions.snifflib.reposconfig.database.RepositoryStorageDOM;
import org.w3c.dom.*;

public class HelloAppDOM extends RepositoryStorageDOM implements HelloAppStorage
{

    public HelloAppDOM()
    {
        super();
    }

    public Class getDOMStorageClass()
    {
        return(HelloAppDOM.class); 
    }
}


Next, we'll define the XML storage class for our HelloApp.

Extend the RepositoryStorageXML Class

Storage as a XML only requires slightly more work, so it's a great next step to take -- and allows true long-term persistence.


The following code simply extends the functionality already provided by Snifflib's RepositoryStorageXML class.

package com.jdoe.myapp.database;

import com.mockturtlesolutions.snifflib.reposconfig.database.RepositoryConnectivity;
import com.mockturtlesolutions.snifflib.reposconfig.database.RepositoryStorageXML;

public class HelloAppXML extends RepositoryStorageXML implements HelloAppStorage 
{
    public HelloAppXML(RepositoryConnectivity conn,String storagename) 
    {
        super(conn,storagename);    
    }

    public Class getDOMStorageClass()
    {
        return(HelloAppDOM.class); 
    }
}


Let's go ahead now and define the XML connection. We don't need to do much here since we just need to implement/override a few key methods already defined in the RepositoryXMLConnection class which we are extending.

package com.jdoe.myapp.database;

import com.mockturtlesolutions.snifflib.reposconfig.database.RepositoryXMLConnection;
import com.mockturtlesolutions.snifflib.reposconfig.database.ReposConfig;

/**
Establishes a connection with XML back-end.
*/
public class HelloAppXMLConnection extends RepositoryXMLConnection implements HelloAppConnectivity
{

    public HelloAppXMLConnection(ReposConfig config,String repos)
    {
        super(config,repos);
    }

    /**
    Identifies the root XML tag to recognize a persisted member of this storage type.
    */
    protected Class getClassForLocalName(String name)
    {
        Class out = null;

        if (name.equals("HelloApp"))
        {
            out = HelloAppXML.class;
        }

        return(out);
    }

    /**
    Returns the XML storage class managed by this connection.
    */
    public Class resolveStorageFor(Class iface)
    {
        Class out = null;

        if (iface.isAssignableFrom(HelloAppStorage.class))
        {
            out = HelloAppXML.class;
        }
        else
        {
            throw new RuntimeException("Unable to resolve storage class for "+iface+".");
        }

        return(out);
    }

}


Now that you have both DOM and XML storage classes defined, you have the ability to work with your storage in real-time/runtime and also a mechanism for long-term persistence on the machine running your program. XML is a good default choice for a first long-term persistence back-end since it is human-readable, portable, fast, and can be viewed directly in most web browsers. There are also lots of XML editors out there in case you need to make adjustments/fixes by hand if all else fails.

Extend the ReposPrefs Class

This is an easy step, but you can always customize preferences for your users all you want at a later time.


You will want to define some preferences for your user's to configure. This is done by extending the ReposPrefs class class.

package com.jdoe.myapp.database;

import java.io.File;
import java.util.LinkedHashMap;
import java.util.HashSet;
import com.mockturtlesolutions.snifflib.reposconfig.database.ReposPrefs;

/**
Preferences for HelloApp repository storage.
*/
public class HelloAppPrefs extends ReposPrefs
{
    public HelloAppPrefs()
    {
        this.ConfigEnvironmentVariable="HELLOAPPPREFS";
        this.UsrConfigFile=(String)System.getProperty("user.home").concat(File.separator).concat(".myhelloappprefs"); //User-specific configuration.
        this.SysConfigFile = System.getenv(this.ConfigEnvironmentVariable);
        this.setSplitString(",");
    }

    public HashSet getFileChooserConfigs()
    {
        HashSet out = super.getFileChooserConfigs();
        out.add("iconmapfile");

        return(out);
    }

    public HashSet getDomainNameFilterConfigs()
    {
        HashSet out = super.getDomainNameFilterConfigs();

        return(out);
    }

    public LinkedHashMap getDefaultConfig()
    {
        LinkedHashMap configmap = new LinkedHashMap();
        configmap.put("lastrepository","default");
        configmap.put("domainname","com.yourdomain");
        configmap.put("iconmapfile",System.getProperty("user.home").concat(File.separator).concat(".myDomainIconMappings"));

        configmap.put("createdon",today());
        configmap.put("createdby",userName());
        configmap.put("helloappcommenttemplates",System.getProperty("user.home").concat(File.separator).concat(".helloappcommenttemplates"));

        configmap.put("helloappcommentview",defaultTexViewer());
        return(configmap);
    } 
}


You are now ready to build!

Build the Application

Go back to the main directory containing build.xml and compile your library which now includes the base code for your HelloApp.

>> ant compile


Or, if you are feeling ambitious, you can go ahead and produce a fat jar file for your library and application.

>> ant makejar


Recall, If you ever forget all the cool stuff your Ant build can do for you just run

>> ant -p

Test it Out in Groovy

Let's test what we've done so far using Groovy. After running ant makejar you should copy the HelloSnifflib-1.0.jar from the release/ directory into your ~/.groovy/lib directory. Be sure to have followed the instructions on setting up a groovy startup script to make sure your libraries will load properly. Open up groovyConsole and enter the following lines of code.

import com.jdoe.myapp.database.*;

X = new HelloAppDOM();

X.setCreatedBy("John Q. Doe");
X.setCreatedOn("01/14/2013");
X.setNickname("com.jdoe.helloworld");
X.setComment("This is the first HelloApp instance I've ever created.");

X.show();


After executing the script, you should see something like the following output resulting from the call to show() method

    <?xml version="1.0" encoding="UTF-8" standalone="no"?><RepositoryStorage CreatedBy="John Q. Doe" CreatedOn="01/14/2013" Enabled="1" Nickname="com.jdoe.helloworld"><Comment>This is the first HelloApp instance I've ever created.</Comment></RepositoryStorage>


Recall that DOM storage only exists during runtime and will cease to exist at the termination of our program. Let's suppose we want to persist com.jdoe.helloworld so that we might access it at a completely different time during another program. We will use the HelloAppXML storage class to do this. Modify your Groovy script to now look like this:

import com.jdoe.myapp.database.*;

X = new HelloAppDOM();

X.setCreatedBy("John Q. Doe");
X.setCreatedOn("01/14/2013");
X.setNickname("com.jdoe.helloworld");
X.setComment("This is the first HelloApp instance I've ever created.");

X.show();

Conf = new HelloAppConfig();
//For first-time run, answer yes to creation questions.
Conf.initialize();
Conn = new HelloAppXMLConnection(Conf,"default");

//For first-time run, answer yes to creation questions.
Y = new HelloAppXML(Conn,"com.jdoe.helloworld");

Y.show(); //Before transfer to XML back-end.

Y.transferStorage(X);

Y.show(); //After transfer to XML back-end.

//Now read back into memory from XML database to verify retrieval of persisted data.

System.out.println("Verifying long-term persistence...");
Z = new HelloAppXML(Conn,"com.jdoe.helloworld");
Z.show();


While running this new code you will be queried about whether you want to create the XML database etc. Just answer yes to those and allow the script to complete. You should see something like the following output:

<?xml version="1.0" encoding="UTF-8" standalone="no"?><RepositoryStorage CreatedBy="John Q. Doe" CreatedOn="01/14/2013" Enabled="1" Nickname="com.jdoe.helloworld"><Comment>This is the first HelloApp instance I've ever created.</Comment></RepositoryStorage>

<?xml version="1.0" encoding="UTF-8" standalone="no"?><RepositoryStorage CreatedBy="Nobody" CreatedOn="2013-01-14" Enabled="1" Nickname="com.jdoe.helloworld"><Comment>Put comment here.</Comment></RepositoryStorage>

<?xml version="1.0" encoding="UTF-8" standalone="no"?><RepositoryStorage CreatedBy="John Q. Doe" CreatedOn="01/14/2013" Enabled="1" Nickname="com.jdoe.helloworld"><Comment>This is the first HelloApp instance I've ever created.</Comment></RepositoryStorage>


To verify that the data is indeed long-term persisted as XML you may open the file com.jdoe.helloworld.xml which should now be located in your ~/helloAppDB/ directory. To get a good look at the file, open it with your favorite web browser. The image below is the view of that file using Google's Chrome browser.

com.jdoe.helloworld.xml in Chrome Browser


For your convenience, the entire source tree for the HelloApp example discussed above can be downloaded here:


Further Extend the RepositoryStorage Interface to Meet Your Needs

Now that you've got a long-term persistence example working, you might want to expand the functionality of your HelloApp. Let's allow for a unique message for each HelloApp storage. Recall that the HelloAppStorage interface extends Snifflib's RepositoryStorage interface. HelloAppStorage is the interface to add methods to to increase the functionality of our HelloApp storage. Modify HelloAppStorage.java as follows:

package com.jdoe.myapp.database;

import com.mockturtlesolutions.snifflib.reposconfig.database.RepositoryStorage;

/**
Interface methods that must be implemented by all HelloApp storage classes.
*/
public interface HelloAppStorage extends RepositoryStorage
{
    /**
    Sets the hello message.
    */  
    public void setHelloMessage(String msg);

    /**
    Gets the hello message.
    */
    public String getHelloMessage();
}


Now we must implement these methods in the storage classes HelloAppXML and HelloAppDOM. Let's deal with HelloAppDOM first. The changes to the DOM are somewhat obvious and explained in comments. The mofications to the DOM are done through the standard API of the W3C as noted by the importation of org.w3c.dom.

package com.jdoe.myapp.database;

import com.mockturtlesolutions.snifflib.reposconfig.database.RepositoryStorageDOM;
import org.w3c.dom.*;

/**
DOM storage class for HelloApp.
*/
public class HelloAppDOM extends RepositoryStorageDOM implements HelloAppStorage
{

    public HelloAppDOM() 
    {
        super();
    }

    public Class getDOMStorageClass()
    {
        return(HelloAppDOM.class); 
    }

    /**
    Let's the super class know that there is a transfer agent which will
    supply some transfer of information for this class.
    */
    public Class getStorageTransferAgentClass()
    {
        return(HelloAppTransferAgent.class);
    }

    /**
    These transfers, required to persisting under the HelloAppStorage interface,
    get added into the transfers resulting from a call to the transferStorage() method.
    */
    protected void specifyStorage(Document doc, Element root)
    {
        //Add to the DOM a text node to hold the hello message.
        Element msg = (Element)document.createElement("Message");
            msg.appendChild(document.createTextNode("Put your message here."));

        root.appendChild(msg);
    }

    /**
    Sets the hello message.
    */
    public void setHelloMessage(String n)
    {
        NodeList msgnodes = this.storageRoot.getElementsByTagName("Message");
        Text text = (Text)((Element)(msgnodes.item(0))).getFirstChild();
        //Handle a possible NULL result situation.
        if (n == null)
        {
            n = "";
        }
        text.setNodeValue(n);
    }

    /**
    Gets the hello message.
    */
    public String getHelloMessage()
    {
        String out = null;
        NodeList msgnodes = this.storageRoot.getElementsByTagName("Message");

        if (msgnodes != null)
        {
            if (msgnodes.getLength() > 0)
            {
                Text textNode = (Text)((Element)msgnodes.item(0)).getFirstChild();

                if (textNode != null)
                {
                    out = textNode.getNodeValue();
                }
            }
        }

        return(out);
    }
}


Next, we will deal with implementing those same new methods in the XML storage class. Since the default implementation of RepositoryStorageXML is itself backed by a DOM, these changes will largely delegate to existing methods provided by the Snifflib library and be even easier to implement.

package com.jdoe.myapp.database;

import com.mockturtlesolutions.snifflib.reposconfig.database.RepositoryStorageXML;
import com.mockturtlesolutions.snifflib.reposconfig.database.RepositoryConnectivity;

public class HelloAppXML extends RepositoryStorageXML implements HelloAppStorage 
{
    public HelloAppXML(RepositoryConnectivity conn,String storagename) 
    {
        super(conn,storagename);    
    }

    public Class getDOMStorageClass()
    {
        return(HelloAppDOM.class); 
    }

    /**
    Let's the super class know that there is a transfer agent which will
    supply some transfer of information for this class.
    */
    public Class getStorageTransferAgentClass()
    {
        return(HelloAppTransferAgent.class);
    }

    /**
    Sets the hello message.
    */
    public void setHelloMessage(String n) 
    {
        ((HelloAppStorage)this.getDOM()).setHelloMessage(n);
        this.writeXML(this.getXMLFilename());
    }

    /**
    Gets the hello message.
    */
    public String getHelloMessage() 
    {
        return(((HelloAppStorage)this.getDOM()).getHelloMessage());
    }   
}


Delegating to a TransferAgent

Finally, we will implement generic method calls needed to transfer information between two HelloApp storages by using a transfer agent. Again the comments within the code explain what happens to negotiate transfer of the HelloApp's message generically for all classes implementing the HelloAppStorage interface.

package com.jdoe.myapp.database;

import com.mockturtlesolutions.snifflib.reposconfig.database.RepositoryStorageTransferAgent;
import com.mockturtlesolutions.snifflib.reposconfig.database.RepositoryStorage;

/**
Class to handle higher-level transfer method calls which depend on lower-level methods
already implemented in the various back-end storage classes.
*/
public class HelloAppTransferAgent  extends RepositoryStorageTransferAgent
{
    public HelloAppTransferAgent(RepositoryStorage a)
    {
        //Note that this constructor sets the protected class variable
        //"target" to the input HelloAppStorage.  We will see this variable 
        //being used to good effect in the transferStorageCommands() method 
        //below.
        super(a);
    }

    /**
    Handles transfer of contents during a call to transferStorage().
    */
    public void transferStorageCommands(RepositoryStorage that)
    {
        //Delegate as much as possible to the super class' transferStorage() method. 
        super.transferStorageCommands(that);

        //Now deal with transfering specific to the HelloAppStorage API.
        //Ensure we have variables of the correct type by casting check.
        HelloAppStorage x = (HelloAppStorage)that;
        ((HelloAppStorage)this.target).setHelloMessage(x.getHelloMessage());
    }
}

Verifying Changes Work

To see that all of this new code is working go back to your build directory where your build.xml file resides and run ant again:

>> ant makejar


Testing it all out can again be done quickly within a little Groovy script.


Important: After you build with ant in the above step be sure to copy the new HelloSnifflib-1.0.jar from your release/ folder into your ~/.groovy/lib/ folder. If you don't do this Groovy will obviously be using the outdated release of your jar file which does not have the latest code changes.


Working from the script we had going earlier, modify the groovy script as follows:

import com.jdoe.myapp.database.*;

X = new HelloAppDOM();

X.setCreatedBy("John Q. Doe");
X.setCreatedOn("01/14/2013");
X.setNickname("com.jdoe.helloworld");
X.setComment("This is the first HelloApp instance I've ever created.");

X.setHelloMessage("Top of the morning to you!");

X.show();

Conf = new HelloAppConfig(); 
//For first-time run, answer yes to creation questions.
Conf.initialize();
Conn = new HelloAppXMLConnection(Conf,"default");

//For first-time run, answer yes to creation questions.
Y = new HelloAppXML(Conn,"com.jdoe.helloworld");
Y.show(); //Before transfer
Y.transferStorage(X);
Y.show(); //After transfer

//Now read back into memory from XML database to verify retrieval of persisted data.

System.out.println("Verifying long-term persistence...");
Z = new HelloAppXML(Conn,"com.jdoe.helloworld");
Z.show();


To verify that the message transfer has indeed worked you should now inspect the file com.jdoe.helloworld.xml in your ~/helloAppDB/ folder using your favorite web browser or XML editor. Here is a view of that file using Google Chrome:

The new com.jdoe.helloworld.xml in Chrome Browser


For your convenience, the entire source tree for the modified HelloApp example discussed above can be downloaded here:



Related

Wiki: Building With Ant
Wiki: Home
Wiki: Repository Framework

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.