Working with Associations

This section shows how to work with an Association. In rdfs terms an association represents an Object property. It simply means that the property value is another graph node. Or in Java terms, the type of the property is a java class that Topaz understands to be an Entity.

To illustrate this, let us add a creator element to Photo. For this example, we'll assume that creator is a person and so we can create a foaf:Person to represent that creator. Following is the definition of FoafPerson entity class.

package org.topazproject.examples.photo;

import java.net.URI;

import org.topazproject.otm.annotations.Entity;
import org.topazproject.otm.annotations.GeneratedValue;
import org.topazproject.otm.annotations.Id;
import org.topazproject.otm.annotations.Predicate;
import org.topazproject.otm.annotations.UriPrefix;

@Entity(graph="foaf", types={"foaf:Person", "foaf:Agent"})
@UriPrefix("foaf:")
public class FoafPerson {
  private URI id;
  private String givenname, surname;

  public URI getId() {return id;}
  @Id
  @GeneratedValue(uriPrefix="foaf:Person/Id/")
  public void setId(URI id) {this.id = id;}

  public String getGivenname() {return givenname;}
  @Predicate()
  public void setGivenname(String name) {this.givenname = name;}

  public String getSurname() {return surname;}
  @Predicate()
  public void setSurname(String name) {this.surname = name;}
}

Note that there is no additional configuration required for Topaz to pickup this new class. The topaz.xml marker that we setup earlier is sufficient.

In addition we also modify the package-info.java to add the graph 'foaf' and the alias 'foaf' used in here. The changes are:

@@ -5,12 +5,14 @@
          type = Rdf.mulgara + "XMLSchemaModel"),
   @Graph(id = "prefix", uri = "local:///topazproject#prefix",
          type = Rdf.mulgara + "PrefixGraph"),
-  @Graph(id = "photo", uri = "local:///topazproject#photo")
+  @Graph(id = "photo", uri = "local:///topazproject#photo"),
+  @Graph(id = "foaf",  uri = "local:///topazproject#foaf")
 })
 
 @Aliases({
   @Alias(alias = "dc",       value = Rdf.dc),
-  @Alias(alias = "topaz",    value = Rdf.topaz)
+  @Alias(alias = "topaz",    value = Rdf.topaz),
+  @Alias(alias = "foaf",     value = Rdf.foaf)
 })
 
 package org.topazproject.examples.photo;

New concepts here

Multiple rdf:type values

Note the usage of multiple rdf:type values 'foaf:Agent' and 'foaf:Person'. This is one of multiple ways to add rdf:type values. The other is to define a java interface named FoafAgent? and make FoafPerson? implement it. In this example we don't need FoafAgent? as a separate concept. Hence the definition like this. Later on if a FoafAgent? concept is needed, then that can be added easily and all data created using this class can continue to work.

UriPrefix annotation

The @UriPrefix? annotation allows Topaz to compute the predicate URIs from the property name. Notice that 'uri=...' is omitted from @Predicate annotations for 'givenname' and 'surname'. Topaz will compute the predicate URI for these fields as foaf:givenname and foaf:surname.

GeneratedValue annotation

This lets Topaz to generate unique ID values for FoafPerson?. The ID generator is based on UUID The prefix converts this UUID to a URI.

Adding a creator association

We can now hook this to the Photo.class as the creator.

@@ -3,6 +3,8 @@ package org.topazproject.examples.photo;
 import java.net.URI;
 import java.util.Date;
 
+import org.topazproject.otm.CascadeType;
+import org.topazproject.otm.FetchType;
 import org.topazproject.otm.annotations.Entity;
 import org.topazproject.otm.annotations.Id;
 import org.topazproject.otm.annotations.Predicate;
@@ -12,6 +14,7 @@ public class Photo {
   private URI id;
   private String title;
   private Date date;
+  private FoafPerson creator;
 
   public URI getId() {return id;}
   @Id
@@ -24,4 +27,8 @@ public class Photo {
   public Date getDate(){return date;}
   @Predicate(uri="dc:date")
   public void setDate(Date date) {this.date = date;}
+
+  public FoafPerson getCreator(){return creator;}
+  @Predicate(uri="dc:creator", cascade={CascadeType.peer}, fetch=FetchType.lazy)
+  public void setCreator(FoafPerson creator) {this.creator = creator;}
 }

Notice that associations are specified much the same way as any other property. The additional configuration here for 'cascade' denotes this as a 'peer' graph node. What this means is that all updates and other operations on Photo.class will propagate to the 'creator' FoafPerson? object, except for deletions. This allows the same FoafPerson? object to be used as the creator for multiple Photo.class objects. (Or other objects for that matter)

The FetchType?.lazy designates that this field need to be loaded only when someone actually requires it. The other option here would be to do FetchType?.eager. That would cause the 'creator' FoafPerson? to be loaded everytime Photo is loaded. Since we could be loading a Photo.class object to work with other properties some of the time, the FetchType?.lazy is chosen here.

Note: FetchType?.lazy is the default. So we could have omitted this here.

Updating the PhotoServlet

Rest of the wiring involves creating a PersonService similar in line to PhotoService and also adding 'givenname' and 'surname' fields to the display and in the forms that are submitted. The findPeople() method on the PersonService? is an exanple of using OQL to narrow the search down. (In a later section <insert-link> we'll show how that search can be improved with text search.)

package org.topazproject.examples.photo;

import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.topazproject.otm.OtmException;
import org.topazproject.otm.Session;
import org.topazproject.otm.SessionFactory;
import org.topazproject.otm.query.Results;

public class PersonService {

  public List<FoafPerson> findPeople(Session session, String givenname, 
                  String surname, boolean wildmatch) throws OtmException {
    givenname = (givenname != null) ? givenname.trim() : "";
    surname = (surname != null) ? surname.trim() : "";

    String query = "select p from FoafPerson p";
    String next = " where ";
    if (givenname.length() > 0) {
      query = query + next + " p.givenname = '" + givenname + "'";
      next = " and ";
    }
    if (surname.length() > 0)
      query = query + next + " p.surname = '" + surname + "'";

    Results results = session.createQuery(query + ";").execute();

    List<FoafPerson> people = new ArrayList<FoafPerson>();
    while(results.next()) {
      FoafPerson person = (FoafPerson) results.get(0);
      if (person == null)
        continue;  // can happen when filters are enabled
      if (!wildmatch) {
        String gn = (person.getGivenname() != null) ? person.getGivenname() : "";
        String sn = (person.getSurname() != null) ? person.getSurname() : "";
        // exact match required
        if (!gn.equals(givenname) || !sn.equals(surname))
          continue;
      }
      people.add(person);
    }

    return people;
  }

  public FoafPerson newPerson(String givenname, String surname) {
    givenname = (givenname != null) ? givenname.trim() : "";
    givenname = (givenname.length() == 0) ? null : givenname;
    surname = (surname != null) ? surname.trim() : "";
    surname = (surname.length() == 0) ? null : surname;

    // If both names are empty, then don't create
    if ((givenname == null) && (surname == null))
      return null;

    // Create a new person
    FoafPerson person = new FoafPerson();
    person.setGivenname(givenname);
    person.setSurname(surname);
    return person;
  }
}

The PhotoServlet? is also updated to display the givenname and surname and also to show the list of FoafPerson? objects. Following shows a screen capture of the new options.

Photo Manager Screenshot

Few things to note here:

  • The 'wildcard' match of the name allows looking up a FoafPerson? by either givenname or surname (case sensitive).
  • newPerson() method doesn't need to call setId() on the new FoafPerson? object. The Id generator will create the id.
  • No explicit saveOrUpdate() is required for persisting the FoafPerson? object that is set as the 'creator' of the Photo object. The saves are cascaded by Topaz as per the 'Cascade.peer' setting in the @Predicate annotation on the 'creator' property on Photo.class.
  • The Photo.class and FoafPerson?.class are persisted into different named graphs. (local:///topazproject#photo and local:///topazproject#foaf) But all of that details are handled automatically by Topaz, leaving the application logic free of all the storage details.

The full source code can be downloaded from topaz-photo-example-2.zip. 'mvn jetty:run-war' will run this.

Attachments