Working with Collections

In this section, we'll see how Topaz can be used to persist collections. We'll also see how 'inverse' mappings can be used to show inverse relationships.

The following patch shows both how collections are defined as well as how an inverse mapping is defined:

@@ -1,7 +1,9 @@
 package org.topazproject.examples.photo;
 
 import java.net.URI;
+import java.util.Set;
 
+import org.topazproject.otm.CascadeType;
 import org.topazproject.otm.annotations.Entity;
 import org.topazproject.otm.annotations.GeneratedValue;
 import org.topazproject.otm.annotations.Id;
@@ -13,6 +15,7 @@ import org.topazproject.otm.annotations.UriPrefix;
 public class FoafPerson {
   private URI id;
   private String givenname, surname;
+  private Set<Photo> myPhotos;
 
   public URI getId() {return id;}
   @Id
@@ -26,5 +29,10 @@ public class FoafPerson {
   public String getSurname() {return surname;}
   @Predicate()
   public void setSurname(String name) {this.surname = name;}
+
+  public Set<Photo> getMyPhotos() {return myPhotos;}
+  @Predicate(uri="dc:creator", inverse=Predicate.BT.TRUE,
+             cascade = {CascadeType.child})
+  public void setMyPhotos(Set<Photo> myPhotos) {this.myPhotos = myPhotos;}
 }
 

As you can see this adds a list of Photos created by a person to the FoafPerson? class. Note that there are no additional statements created in the triple-store to represent this. It collects all statements on Photo objects for which this FoafPerson? is the 'dc:creator' and adds to the 'myPhotos' set.

New concepts here

Collection mapping

Notice that there is no additional configuration required for Topaz to manage the 'myPhotos' collection. Topaz understands java.util.Collection objects and understands that the maximum cardinality on the domain of 'dc:creator' is greater than one. (domain, because of the inverse mapping explained below). In later chapters <insert-link> you'll find how collections can be persisted as rdf:list or rdf:bag.

Also note that instead of a java.util.Set, we could have used an array here and Topaz will persist it in the same way.

Inverse mapping

The attributes 'inverse=Predicate.BT.TRUE' indicates to Topaz that the id field appears as the 'object' in an RDF statement as opposed to the 'subject'. Topaz will know from the Association definition for Photo, the 'dc:creator' statements are all in the named graph 'photo' as opposed to the 'foaf' graph that other statements about foaf:Person objects are stored.

There are no additional statements created in the triple-store to represent an inverse mapped property in this example. This is because, the 'creator' property in the Photo.class shares the same RDF statement. In this example, both Photo.class and Foaf.class will create/delete these statements. However Topaz allows finer control on making the access to read only. This is achived by setting the 'notOwned=Predicate.BT.TRUE' for the @Predicate attribute. The notOwned would normally be set if the property is referring back to a 'parent' property.

It is worth noting that the cascading options for a property is independent of the the 'inverse' mapping or the 'notOwned' setting. This means when a FoafPerson? object is deleted, all Photo objects for which the deleted FoafPerson? is the 'creator' are also deleted as you would expect.

Updating PhotoServlet

PhotoServlet is updated to display the list of Photos. The only noteworthy thing there is that since both the Photo.creator and FoafPerson.myPhotos are referring to the same piece of information, update of either one will make the other out of sync. Topaz does not automatically track and propagate the change from one to the other.

In the updated PhotoServlet, we have taken the approach where 'myPhotos' list is never modified by the Servlet. Therefore before being used, the list is always refreshed by calling the 'refresh' method of the Topaz Session. The changes are as follows:

@@ -6,6 +6,7 @@ import java.io.PrintWriter;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.List;
+import java.util.Set;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
@@ -204,9 +205,18 @@ public class PhotoServlet extends HttpServlet {
     out.println("<h2>People Manager</h2>");
     out.println("<table border='2'>");
     out.println("<tr><th>givenname</th>");
-    out.println("<th>surname</th><th>action</th></tr>");
+    out.println("<th>surname</th>");
+    out.println("<th>my photos</th>");
+    out.println("<th>action</th></tr>");
 
     for (FoafPerson person : personService.findPeople(session, null, null, true)) {
+      /*
+       * Since we didn't update the myPhotos list on create/delete, we'll ask 
+       * Topaz to refresh. Note that all changes are automatically flush()ed
+       * before executing a query. (like the query executed by findPeople()
+       * above for example)
+       */
+      session.refresh(person);
       String gn = person.getGivenname();
       String sn = person.getSurname();
       if (gn == null)
@@ -216,6 +226,7 @@ public class PhotoServlet extends HttpServlet {
       out.println("<tr><form method='post'>");
       out.println("<td>" + gn + "</td>");
       out.println("<td>" + sn + "</td>");
+      out.println("<td>" + myPhotoList(person.getMyPhotos()) + "</td>");
       out.println("<td><input type='hidden' name='id' value='" + person.getId() +"'>");
       out.println("<input type='submit' name='action' value='delete'></td>");
       out.println("</form></tr>");
@@ -223,4 +234,17 @@ public class PhotoServlet extends HttpServlet {
 
     out.println("</table>");
   }
+
+  protected String myPhotoList(Set<Photo> photos) {
+    if ((photos == null) || (photos.size() == 0))
+      return "";
+
+    StringBuilder s = new StringBuilder();
+    for (Photo p : photos)
+      s.append(p.getId()).append("<br/>");
+
+    s.setLength(s.length() - 5);
+
+    return s.toString();
+  }
 }

Adding a foaf:depicts list

Expanding on the collections and inverse mapping concepts, above let us add a foaf:depicts property to Photo.class. This property represents the people in a photograph. Similarly the inverse relationship of adding the list of photographs a person appears can easily be added.

This illustrates how a 'many-to-many' relationship in both directions can be added with ease in RDF and Topaz - without the complications that you would normally see in relational databases.

--- a/topaz/topaz-photo-example/src/main/java/org/topazproject/examples/photo/FoafPerson.java
+++ b/topaz/topaz-photo-example/src/main/java/org/topazproject/examples/photo/FoafPerson.java
@@ -1,6 +1,7 @@
 package org.topazproject.examples.photo;
 
 import java.net.URI;
+import java.util.HashSet;
 import java.util.Set;
 
 import org.topazproject.otm.CascadeType;
@@ -15,7 +16,8 @@ import org.topazproject.otm.annotations.UriPrefix;
 public class FoafPerson {
   private URI id;
   private String givenname, surname;
-  private Set<Photo> myPhotos;
+  private Set<Photo> myPhotos = new HashSet<Photo>();
+  private Set<Photo> depictedIn = new HashSet<Photo>();
 
   public URI getId() {return id;}
   @Id
@@ -34,5 +36,10 @@ public class FoafPerson {
   @Predicate(uri="dc:creator", inverse=Predicate.BT.TRUE,
              cascade = {CascadeType.child})
   public void setMyPhotos(Set<Photo> myPhotos) {this.myPhotos = myPhotos;}
+
+  public Set<Photo> getDepictedIn() {return depictedIn;}
+  @Predicate(uri="foaf:depicts", inverse=Predicate.BT.TRUE,
+             cascade = {CascadeType.child})
+  public void setDepictedIn(Set<Photo> depictedIn) {this.depictedIn = depictedIn;}
 }
 
diff --git a/topaz/topaz-photo-example/src/main/java/org/topazproject/examples/photo/Photo.java b/topaz/topaz-photo-example/src/main/java/org/topazproject/examples/photo/Photo.java
index f13769d..8cc0894 100644
--- a/topaz/topaz-photo-example/src/main/java/org/topazproject/examples/photo/Photo.java
+++ b/topaz/topaz-photo-example/src/main/java/org/topazproject/examples/photo/Photo.java
@@ -2,6 +2,8 @@ package org.topazproject.examples.photo;
 
 import java.net.URI;
 import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
 
 import org.topazproject.otm.CascadeType;
 import org.topazproject.otm.FetchType;
@@ -15,6 +17,7 @@ public class Photo {
   private String title;
   private Date date;
   private FoafPerson creator;
+  private Set<FoafPerson> depictedPeople = new HashSet<FoafPerson>();
 
   public URI getId() {return id;}
   @Id
@@ -31,4 +34,12 @@ public class Photo {
   public FoafPerson getCreator(){return creator;}
   @Predicate(uri="dc:creator", cascade={CascadeType.peer}, fetch=FetchType.lazy)
   public void setCreator(FoafPerson creator) {this.creator = creator;}
+
+  public Set<FoafPerson> getDepictedPeople(){
+    return depictedPeople;
+  }
+  @Predicate(uri="foaf:depicts", cascade={CascadeType.peer}, fetch=FetchType.lazy)
+  public void setDepictedPeople(Set<FoafPerson> depictedPeople) {
+    this.depictedPeople = depictedPeople;
+  }
 }

Running it

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

Additional things that you can try adding here is a 'Location' attribute and 'Event' attribute or other attributes for describing the photographs better. Please see http://www.w3.org/2005/Incubator/mmsem/XGR-image-annotation/ for additional vocabularies.

All of these additional statements allows you to make your personal photo collection even more powerful to search. 'photos of a person in a specific date range' or 'photographs in which all your family members are present in the vacation that you took' or 'all pictures from a birthday party' to name just a few.

Attachments