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.
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.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.




Help






