Jump to content

Enterprise JavaBeans 3.1: The Singleton Session Bean, An Example

0
  chco's Photo
Posted Oct 28 2010 07:35 AM

The following excerpt from the O'Reilly publication Enterprise JavaBeans 3.1, Sixth Edition offers an example of the singleton session bean.

Because the singleton bean represents one instance to be used by all incoming requests, we may take advantage of the extremely small impact this session type will have upon our overall memory consumption. The factors to beware are state consistency (thread-safety) and the potential blocking that this might impose. Therefore, it’s best to apply this construct in high-read environments.

One such usage may be in implementing a simple high-performance cache. By letting parallel reads tear through the instance unblocked, we don’t have the overhead of a full pool that might be supplied by an SLSB. The internal state of a cache is intended to be shared by many distinct sessions, so we don’t need the semantics of an SFSB. Additionally, we may eagerly populate the cache when the application deploys, before any requests hit the system.

For this example, we’ll define an RSS Caching service to read in a Really Simple Syndication (RSS; http://en.wikipedia.org/wiki/RSS) feed and store its contents for quick access. Relatively infrequent updates to the cache will temporarily block incoming read requests, and then concurrent operation will resume as normal, as illustrated in Figure 7-8.

Figure 7-8. A concurrent singleton session bean servicing many requests in parallel

Attached Image


Value Objects

As we’ll be representing the RSS entries in some generic form, first we should define what information we’ll expose to the client:

public interface RssEntry
{
   /**
    * Obtains the author of the entry
    *
    * @return
    */
   String getAuthor();

   /**
    * Obtains the title of the entry
    *
    * @return
    */
   String getTitle();

   /**
    * Obtains the URL linking to the entry
    *
    * @return
    */
   URL getUrl();

   /**
    * Obtains the short description of the entry
    *
    * @return
    */
   String getDescription();
}


This is a very simple definition of properties. One thing that’s important to note is that the type java.net.URL is itself mutable. We must take care to protect the internal state from being exported in a read request. Otherwise, a local client, which uses pass-by-reference, may change the contents of the URL returned from getURL(). To this end, we’ll introduce a simple utility to copy the reference such that if a client writes back to it, only his view will be affected:

class ProtectExportUtil
{
   /**
    * Internal constructor; protects against instantiation
    */
   private ProtectExportUtil()
   {
   }

   /**
    * Returns a copy of the specified URL; used to ensure that mutable
    * internal state is not leaked out to clients
    * @param url
    * @return
    */
   static URL copyUrl(final URL url)
   {
      // If null, return
      if (url == null)
      {
         return url;
      }

      try
      {
         // Copy
         return new URL(url.toExternalForm());
      }
      catch (final MalformedURLException e)
      {
         throw new RuntimeException("Error in copying URL", e);
      }
   }
}


Project ROME (https://rome.dev.java.net/) is an open source framework for dealing with RSS feeds, and we’ll use it to back our implementation of RssEntry. In returning URLs, it will leverage the ProtectExportUtil:

public class RomeRssEntry implements RssEntry
{

   private String author;
   private String description;
   private String title;
   private URL url;

   /**
    * Constructor
    *
    * @param entry The Rome API's RSS Entry representation
    * @throws IllegalArgumentException If the entry is not specified
    */
   RomeRssEntry(final SyndEntry entry) throws IllegalArgumentException
   {
      // Set properties
      this.author = entry.getAuthor();
      final SyndContent content = entry.getDescription();
      this.description = content.getValue();
      this.title = entry.getTitle();
      final String urlString = entry.getLink();
      URL url = null;
      try
      {
         url = new URL(urlString);
      }
      catch (final MalformedURLException murle)
      {
         throw new RuntimeException("Obtained invalid URL from Rome RSS entry: " 
+ entry, murle);
      }
      this.url = url;
   }

   @Override
   public String getAuthor()
   {
      return this.author;
   }

   @Override
   public String getDescription()
   {
      return this.description;
   }

   @Override
   public String getTitle()
   {
      return this.title;
   }

   @Override
   public URL getUrl()
   {
      return ProtectExportUtil.copyUrl(this.url);
   }

   /* (non-Javadoc)
    * @see java.lang.Object#toString()
    */
   @Override
   public String toString()
   {
      final StringBuilder sb = new StringBuilder();
      sb.append(this.getTitle());
      sb.append(" - ");
      sb.append(this.url.toExternalForm());
      return sb.toString();
   }
}




That should cover the case of the value object our EJB will need in order to return some results to the client. Note that the ROME implementation is completely separated from the contracted interface. Should we choose to use another library in the future, our clients won’t require any recompilation.

The Contract: Business Interfaces

Now we need to define methods to read the cache’s contents, obtain the URL that hosts the RSS feed, and refresh the cache from the URL:

public interface RssCacheCommonBusiness
{
   /**
    * Returns all entries in the RSS Feed represented by
    * {@link RssCacheCommonBusiness#getUrl()}.
    * This list will not support mutation and is read-only.
    */
   List<RssEntry> getEntries();

   /**
    * Returns the URL of the RSS Feed
    *
    * @return
    */
   URL getUrl();

   /**
    * Flushes the cache and refreshes the entries from the feed
    */
   void refresh();
}


Again, we must take care to protect the internal cache state from being exported in a read request. The point in question here is the List returned from getEntries(). Therefore we note in the documentation that the reference return will be read-only.

The refresh() operation will obtain the contents from the URL returned by getURL(), and parse these into the cache.

The cache itself is a List of the RssEntry type.

Bean Implementation Class

Once again, the bean implementation class contains the meat and potatoes of the business logic, as well as the annotations that round out the EJB’s metadata:

@Singleton
@Startup
@Remote(RssCacheCommonBusiness.class)
// Explicitly declare Container Managed Concurrency, which is unnecessary; it's 
the default
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
public class RssCacheBean implements RssCacheCommonBusiness {


The declaration marks our class as a Singleton Session type that should be initialized eagerly upon application deployment. We’ll use container-managed concurrency (which is defined explicitly here for illustration, even though this is the default strategy).

Some internal members are next:

/**
    * Logger
    */
   private static final Logger log = Logger.getLogger(RssCacheBean.class);

   /**
    * URL pointing to the RSS Feed
    */
   private URL url;

   /**
    * Cached RSS Entries for the feed
    */
   private List<RssEntry> entries;


The URL here will be used to point to the RSS feed we’re caching. The List is our cache. Now to implement our business methods:

/* (non-Javadoc)
    * @see org.jboss.ejb3.examples.ch07.rsscache.spi.RssCacheCommonBusiness
#getUrl()
    */
   @Lock(LockType.READ)
   @Override
   public URL getUrl()
   {
      // Return a copy so we don't export mutable state to the client
      return ProtectExportUtil.copyUrl(this.url);
   }


Again, we protect the internal URL from being changed from the outside. The lock type is READ and so concurrent access is permitted, given no WRITE locks are currently held:

/**
    * @see org.jboss.ejb3.examples.ch07.rsscache.spi.RssCacheCommonBusiness
#refresh()
    * @throws IllegalStateException If the URL has not been set
    */
   @PostConstruct
   @Override
   // Block all readers and writers until we're done here;
   // Optional metadata, WRITE is the default
   @Lock(LockType.WRITE)
   public void refresh() throws IllegalStateException
   {

      // Obtain the URL
      final URL url = this.url;
      if (url == null)
      {
         throw new IllegalStateException("The Feed URL has not been set");
      }
      log.info("Requested: " + url);

      // Obtain the feed
      final FeedFetcher feedFetcher = new HttpClientFeedFetcher();
      SyndFeed feed = null;
      try
      {
         feed = feedFetcher.retrieveFeed(url);
      }
      catch (final FeedException fe)
      {
         throw new RuntimeException(fe);
      }
      catch (final FetcherException fe)
      {
         throw new RuntimeException(fe);
      }
      catch (final IOException ioe)
      {
         throw new RuntimeException(ioe);
      }

      // Make a new list for the entries
      final List<RssEntry> rssEntries = new ArrayList<RssEntry>();

      // For each entry
      @SuppressWarnings("unchecked")
      // The Rome API doesn't provide for generics, so suppress the warning
      final List<SyndEntry> list = (List<SyndEntry>) feed.getEntries();
      for (final SyndEntry entry : list)
      {
         // Make a new entry
         final RssEntry rssEntry = new RomeRssEntry(entry);

         // Place in the list
         rssEntries.add(rssEntry);
         log.debug("Found new RSS Entry: " + rssEntry);
      }

      // Protect the entries from mutation from exporting the client view
      final List<RssEntry> protectedEntries = Collections.unmodifiableList
(rssEntries);

      // Set the entries in the cache
      this.entries = protectedEntries;
   }




There’s a lot going on here. @PostConstruct upon the refresh() method means that this will be invoked automatically by the container when the instance is created. The instance is created at application deployment due to the @Startup annotation. So, transitively, this method will be invoked before the container is ready to service any requests.

The WRITE lock upon this method means that during its invocation, all incoming requests must wait until the pending request completes. The cache may not be read via getEntries() during refresh().

Also very important is that our cache is made immutable before being set. Accomplished via Collections.unmodifiableList(), this enforces the read-only contract of getEntries().

The remainder of the method body simply involves reading in the RSS feed from the URL and parsing it into our value objects.

Cover of Enterprise JavaBeans 3.1
Learn more about this topic from Enterprise JavaBeans 3.1, 6th Edition. 

Learn how to code, package, deploy, and test functional Enterprise JavaBeans with the latest edition of bestselling guide. Written by the developers of the JBoss EJB 3.1 implementation, this book brings you up to speed on each of the component types and container services in this technology, while the workbook in the second section provides several hands-on examples for putting the concepts into practice. Enterprise JavaBeans 3.1 is the most complete reference you'll find on this specification.

Learn More Read Now on Safari


Tags:
0 Subscribe


0 Replies