Working with Blobs

So far we have been concentrating on how to maintain metadata about photographs in your photo collection. The photographs themselves are stored elsewhere. While this capability itself is quite useful, we'll show how a BlobStore can be used to store the photographs themselves.

See wiki:Topaz/Manual/Section06#Blobs to learn more about Blobs in Topaz.

Configuring the BlobStore

Blob stores that are transactional and meets the requirements laid out above is the goal of the Akubra Project. However there are currently no plugins for Topaz to make use of Akubra. For now the two BlobStore? implementations that are avialable are:

  • SimpleBlobstore packaged with Topaz itself.
  • A blob-store abstraction over Fedora commons 2.1 with patches from Topaz to make it work as a blob-store. This option may be deprecated in favor of the Akubra project and may only be used when the embedded SimpleBlobStore solution is not an option. (eg. in a load-sharing multi-server environment)

For our example application, the SimpleBlobStore is more than sufficient. The configuration is fairly straight-forward. It requires a root-directory with adequate storage to store the Blobs. That is about it:

@@ -9,6 +9,7 @@ import org.topazproject.otm.Session;
 import org.topazproject.otm.SessionFactory;
 import org.topazproject.otm.impl.SessionFactoryImpl;
 import org.topazproject.otm.stores.ItqlStore;
+import org.topazproject.otm.stores.SimpleBlobStore;
 
 public class TopazConfigurator {
   private SessionFactory factory = new SessionFactoryImpl();
@@ -18,6 +19,10 @@ public class TopazConfigurator {
     tqlFactory.setDbDir(System.getProperty("java.io.tmpdir") + "/triple-db");
     ItqlStore tripleStore = new ItqlStore(URI.create("local:///topazproject"), tqlFactory);
     factory.setTripleStore(tripleStore);
+
+    SimpleBlobStore blobStore = new SimpleBlobStore(System.getProperty("java.io.tmpdir") + "/blob-db");
+    factory.setBlobStore(blobStore);
+
     factory.preloadFromClasspath();
     factory.validate();
     createGraphs();

Configuring the Representation Entity

For our photo example, the goal is to store the various representations of photos in our simple photo collection. These representations are tagged for easy identification (eg. 'original', 'thumbnail' etc.) and could all have different content-types. The following class captures these requirements:

package org.topazproject.examples.photo;

import java.net.URI;
import java.util.Set;

import org.topazproject.otm.CascadeType;
import org.topazproject.otm.Blob;
import org.topazproject.otm.annotations.Entity;
import org.topazproject.otm.annotations.GeneratedValue;
import org.topazproject.otm.annotations.Id;
import org.topazproject.otm.annotations.Predicate;

@Entity(graph="photo", types={"topaz:Representation"})
public class Representation {
  private URI id;
  private Set<String> tags;
  private Blob  image;
  private Photo photo;
  private String contentType;

  public URI getId() { return id; }
  @Id
  @GeneratedValue
  public void setId(URI id) { this.id = id; }

  public Set<String> getTags() { return tags; }
  @Predicate(uri = "topaz:tag")
  public void setTags(Set<String> tags) { this.tags = tags; }

  public Blob getImage() { return image; }
  @org.topazproject.otm.annotations.Blob
  public void setImage(Blob image) { this.image = image; }

  public Photo getPhoto() {return photo;}
  @Predicate(uri="topaz:representation", inverse=Predicate.BT.TRUE,
      notOwned=Predicate.BT.TRUE, cascade={CascadeType.peer})
  public void setPhoto(Photo photo) {this.photo = photo;}

  public String getContentType() {
    return contentType;
  }
  @Predicate(uri = "topaz:contentType")
  public void setContentType(String contentType) {
    this.contentType = contentType;
  }
}

New concepts here:

  • the @Blob annotation for the field that is holding the Blob. This is how a field is marked as a Blob.
  • the Topaz Blob class that supports streaming. The other option is to use the javax.activation.DataSource?. However in most cases the extended API that is provided by the Blob class comes in handy and therefore that is what we use in this example.

The rest are concepts that we have covered before. Note that a back-pointer to the Photo.class is maintained for easy access to the Photo this belongs to. The Photo.class similarly maintains all its representations as a set:

@@ -18,6 +18,7 @@ public class Photo {
   private Date date;
   private FoafPerson creator;
   private Set<FoafPerson> depictedPeople = new HashSet<FoafPerson>();
+  private Set<Representation> representations = new HashSet<Representation>();
 
   public URI getId() {return id;}
   @Id
@@ -42,4 +43,21 @@ public class Photo {
   public void setDepictedPeople(Set<FoafPerson> depictedPeople) {
     this.depictedPeople = depictedPeople;
   }
+
+  public Set<Representation> getRepresentations() {
+    return representations;
+  }
+  @Predicate(uri="topaz:representation", cascade={CascadeType.child})
+  public void setRepresentations(Set<Representation> representations) {
+    this.representations = representations;
+  }
+
+  public Representation findRepresentation(String tag) {
+    for (Representation rep : representations)
+      for (String t : rep.getTags())
+        if (t.equals(tag))
+          return rep;
+
+    return null;
+  }
 }

Wiring up photo upload capability

To upload images we make use of commons file-upload library. In maven all we need to do is to update the dependency in our pom.xml to include this in the build:

@@ -71,6 +71,12 @@
       <version>2.4</version>
       <scope>provided</scope>
     </dependency>
+    <!-- For uploading photos -->
+    <dependency>
+      <groupId>commons-fileupload</groupId>
+      <artifactId>commons-fileupload</artifactId>
+      <version>1.2.1</version>
+    </dependency>

Last step is to implement image uploads and display in PhotoServlet?. The implementation here is to show the concepts involved only and therefore the UI is rudimentary.

We are making use of the Streaming API of commons-file-upload since this example can accept images (or even video) that could be really large in size. Because of this the upload handling logic is a bit scattered. The following therefore shows you the logical order in which an upload is performed:

public Representation createRepresentation(Session session, Photo photo, Set<String> tags. 
          InputStream stream, String contentType) throws OtmException, IOException {
  Representation rep = new Representation();
  rep.setTags(tags);
  rep.setContentType(contentType);

  rep.setPhoto(photo);
  photo.getRepresentations().add(rep);

 /*
  * Need to explicitly save here so that Topaz allocates the Blob.
  */
  session.saveOrUpdate(rep);

  OutputStream out = rep.getImage().getOutputStream();
  IOUtils.copyLarge(stream, out);
  out.close();
  
  return rep;
}

Important

Note the saveOrUpdate step above. This triggers Topaz to create a Blob object and place it in the 'image' property of the Representation.class. If the image field was a byte[] array the logic would have been simpler as in:

   rep.setImage(readAll(stream));

Downloading

Downloading is a much simpler case. The Blob contents are read using the getInputStream() method and written out to the servlet-output stream.

An additional thing that can be easily done here is to store the content-length also in the Representation.class so that we don't need to use chunked transfer encoding.

Running it

The full source code can be downloaded from topaz-photo-example-4.zip. 'mvn jetty:run-war' will run this. Point your browser to http://localhost:8080/topaz-photo-example/photoManager to try it out.

Attachments