Installing Murq

Installation is as simple as putting the murq.jar and its dependencies on your classpath.

Building From Source

Building from source required the Ant build tool. Once ant is installed, you will need to do one of the following:

  1. Define the environment variable JAVA_LIB_HOME with the value being the directory where all of the required dependencies are located.
  2. Override the location of the dependencies in default.properties in any of the following locations: build.properties, $USER_HOME/build.properties, $USER_HOME/murq.build.properties

Once this is done, simply run the ant task: dist. This will create the murq.jar under the $BASEDIR/dist/murq-[version] directory.

Getting Started

Creating A Murq Instance

The Murq object is the point at which almost all interaction with the component will take place. Every instance of Murq, however, must be backed by an ISource.

An ISource is the interface by which Murq actually persists objects and binary data. These can take many forms, persisting to such structures as a file system, a zip file or an RDBMS.

For the sake of simplicity, we will assume we want to persist to a file system directory. To do so, we would create a Murq instance like this:

ISource src = new FSSource("/data");
Murq murq = new Murq(src);

The same code is used to create a new ISource as is used to access an already existing one.

If we wanted to create a password protected instance, we would simply modify the constructor like this:

Murq murq = new Murq(src,"mySecretPassword");

NOTE: Creating two Murq instances that use the same ISource will result in nondeterministic behavior. Also, when using password protection, be aware that passwords cannot be recovered if lost and will result in total loss of persisted data.

Persisting Objects

Objects to be persisted by Murq need only implement a simple interface: IContent. This interface allows Murq to assign identifiers by which objects can be retrieved and to detect such things as concurrent modification conflicts. The ContentAdapter class is provided as a convenience.

Continuing our previous example, let's imagine we have written a Book.class to hold information for a local library. Book implements IContent, so it can be persisted by Murq. Here is how we might do it:

Book b = new Book();
b.setTitle("A Tale of Two Cities");
b.setAuthor("Charles Dickens");
b.setDescription("A moral novel strongly concerned with themes of guilt,"+
                 "shame, redemption and patriotism.");

murq.put(b);

Persisting Binary Data

To persist arbitrary binary data (such as a jpeg of your dog) with Murq, it must be accompanied by an object containing information about it, such as its size, filename and the same properties required for all other content. This descriptor must implement IBinaryContent. The BinaryContentAdapter class is provided as a convenience.

So let's persist an image of the book's cover from the example:

BookCover bc = new BookCover();
bc.setFilename("tale_two_cities.jpg");

InputStream in = new FileInputStream("tale_two_cities.jpg");

//You'll notice that when dealing with binaries, we use read and write
//instead of put and get.
murq.write(bc,in);
in.close();

Loading Objects

Once an object has been persisted, Murq sets the contentId property of that object. Thus, to get back a copy of our Book as it was when we persisted it, we could do the following:

String bookId = b.getContentId();
Book persistedCopy = (Book)murq.get(bookId);

Loading Binary Data

Loading the content object describing binary data is performed like any other, allowing for the description to be fetched without paying the price of loading the actual data. In order to read the actual binary data, we must do the following:

String coverId = bc.getContentId();
OutputStream out = new FileOutputStream("cover.jpg");

murq.read(coverId,out);
out.close;

The above will read the persisted binary data and copy it to the OutputStream provided.

To ensure that all resources are properly closed, always be sure to close Murq instances when no longer needed like this:

murq.close();

Linking

There are times you may need repeated access to a few specific content objects. While it is true that they could be found by performing a search (covered later in this guide), why go through that expense? Murq provides a way to create simple name links to frequently used content:

//Get the contentId
String bookId = b.getContentId();
//Create the link
murq.addLink("MyFavoriteBook",bookId);

//We can now get the book without knowing it's contentId
Book linkedBook = (Book)murq.resolveLink("MyFavoriteBook");

Internationalization

What if we need to store descriptions of books in multiple languages for a diverse range of library patrons? No problem:

//Get the contentId of the English object
String bookId = en.getContentId();
//Get a copy from Murq
Book sp = (Book)murq.get(bookId);

//Since this is a copy, we can change the locale with no problem.  This will
//retain the contentId from the original content, indicating that both
//represent the same concept and differ only in language.
sp.setLocale(new Locale("es"));
//Now we enter a new description in Spanish
sp.setDescription("Una novela moral referida fuertemente a temas de la"+
"culpabilidad, de la verguenza, del rescate y del patriotismo.");

//Finally, persist the Spanish version
murq.put(sp);

Now, if we specifically wanted the spanish translation, this is all we do:

Book spCopy = (Book)murq.get(bookId,new Locale("es"));

What if we asked for the Book in French? Since we haven't created a French translation, we would get the default (in this case, English) version. For a more detailed look at how this selection process works, please see the javadocs for the java.util.ResourceBundle class.

Searching

Murq makes use of the Lucene search engine. Thus, we are able to find a book like this:

Set<IContent> results = murq.find("author: dickens");

The example above would return the results for the default language. What if we wanted the description in spanish that we just created?

Set<IContent> results = murq.find("author: dickens",new Locale("es"));

These are very simple examples to get you started. For more detail information about the Lucene query syntax, please visit the Lucene website. Murq also provides a more robust query builder in the MurqQuery object. Please see the javadocs for more information.

Indexing

You may be wondering how the author property of the Book class got indexed. Murq defines a lightweight, optional interface that persisted objects may implement called IIndexable. This interface allows objects to tell Murq what properties need to be indexed, and what kind of indexing to perform on them.

Here is an example of how the IIndexable methods might be implemented in the Book class:

public String getAuthor()...
public String getTitle()...
public String getDescription()...

public Set<String> keywordProperties()
{
  Set<String> rval = new HashSet<String>();

  //The value of the author javabean property should be indexed as a keyword
  rval.add("author");

  return rval;
}

public Set<String>  textProperties()
{
  Set<String> rval = new HashSet<String>();

  //The values of the author, title and description properties should be
  //full text indexed
  rval.add("author");
  rval.add("title");
  rval.add("description");

  return rval;
}

public Set<String>  defaultProperties()
{
  Set<String> rval = new HashSet<String>();

  //The values of the author, title and description properties should be
  //concatenated and indexed for use in default searching
  rval.add("author");
  rval.add("title");
  rval.add("description");

  return rval;
}

Migration

Murq provides a painless way to merge and move entire ISources.

First, let's see how we might add another library's catalog of books to our own:

//Create the other catalog's ISource object
ISource otherCatalog = new ZipSource("catalog.zip");
//And add it to ours
murq.add(otherCatalog);

Keep in mind, when adding, the added ISource's content class files must be on the classpath.

Now, what if we wanted to move our content to another machine? We could do this:

//Create a new, empty source that uses a single zip file
//as storage and get a Murq instance for it
Murq zipMurq = neq Murq(new ZipSource("transport.zip"));
//Now let's get the source for our current catalog
ISource current = new FSSource("/data");

//Now we simply add our existing catalog to the new zip backed instance
zipMurq.add(current);

We could now transport the catalog as the zip file, installing it on the new machine just as we merged the catalog from the first example.