Help

Moved to Turku in -95 to study Computer Science at the Åbo Akademi University and might someday even graduate. Perhaps. I'm currently involved with Seam/Weld, time permitting. and I also use Seam for projects at work in Affecto Finland.

Location: Turku, Finland
Occupation: Software Developer
Archive
11. Jan 2011, 08:02 CET, by Nicklas Karlsson

Not sure if this has been posted but I just noticed that Gavin King has been awarded the title of JCP Star Spec Lead. Congratulations!

Read the press release here

27. Jun 2010, 23:29 CET, by Nicklas Karlsson

While the community is pondering on the question as how to do JSF headless rendering for xhtml templates for content delivery, I started planing on how to make the Excel-centric spreadsheet module from Seam 2 more generic for the upcoming Seam 3 release. This blog is just a documented brainstorming session where I call for feedback so don't panic if you feel you didn't learn anything new once you finished reading.

The model

Excel-generation didn't really have that much of a model in Seam 2. The various UI tags worked pretty much straight against the rendering library, so the first step in a more generic solution would be a model for the spreadsheet. So, how does a spreadsheet application look like? The answer to that is of course It depends on the spreadsheet application but most of them have a collection of named worksheets that has a collection of cells that are formatted in various ways. Let's start with the workbook (I omit getters/setters for brevity):

public class Workbook
{
   private List<Worksheet> worksheets = new ArrayList<Worksheet>();
   private List<FormattingRule> formattingRules = new ArrayList<FormattingRule>();
}

We'll talk about FormattingRules shortly. Now the worksheet:

public class Worksheet
{
   private String name;
   private List<Cell> cells = new ArrayList<Cell>();
   private List<FormattingRule> formattingRules = new ArrayList<FormattingRule>();
}

and the cell:

public class Cell
{
   private Coordinate coordinate;
   private CellSpan span;
   private CellFormatting formatting;
}

a Coordinate is a class for modeling a (column, row) tuple:

public class Coordinate
{
   private int column;
   private int row;
}

and a CellSpan for modeling col/rowspans

public class CellSpan
{
   private int columnSpan;
   private int rowSpan;
}

Formatting

Now for the formatting. This is modeled by the CellFormatting class:

public class CellFormatting
{
   public enum Type { CASCADING, ABSOLUTE }
   public Type type = Type.CASCADING;
   // Lots of formatting objects for Fonts, Borders, Backgrounds etc...
}

Where the enum is used for marking if the formatting should be absolute or merged/cascaded with previous rules. What previous rules? That's what I said I'll get back to when I talked about the Workbook class. Workbooks and SpreadSheets can have a list of FormattingRule:s that are cascaded

public interface FormattingRule
{
   public abstract boolean appliesTo(Cell cell);
   public abstract CellFormatting getCellFormatting();
}

So if you want all cells in the entire Workbook to have a particular font, you would place an implementation of a FormattingRule in the Workbook that appliesTo all cells and returns a formatting for that font. Then if you would like to have alternate rows with grey background in a certain worksheet, you would in that worksheet place a FormattingRule implementation that appliesTo cell.getCoordinate().getRow()s that are odd and return a formatting for grey background. For final tuning of a cell, it could also have some formatting that would be merged in for the final result.

Generating the model

You could of course assemble a Workbook model by hand but that would be a bit tedious so there would of course be builder classes that could transform a JSF datatable (with ICEfaces/RichFaces variants for bling-bling-support) to a Workbook. Another tool would be a Workbook from JavaBean builder that would take an Iterable<Foo> and a String[] of field names and generate a workbook from that (keeping track of Coordinates with an internal cursor) etc.

Importing and exporting

So what do we do with the model? We import and export then of course. We could read a workbook with

public interface SpreadsheetReader
{
   public abstract Workbook readWorkbook(byte[] data);
}

and write one with

public interface SpreadsheetWriter
{
   public abstract byte[] writeWorkbook(Workbook workbook);
   public abstract byte[] writeWorkbook(Workbook workbook, byte[] template);
}

This means that we could have different implementations that do different things. There could be implementations like

@Inject SpreadsheetWriter write;

or

@Inject @Excel SpreadsheetWriter write;

or

@Inject @OpenOffice SpreadsheetWriter write;

or in the case of multiple implementations

@Inject @Excel(implementation="jxl") SpreadsheetWriter write;

The output from the could then be put in a database, written to the user, mailed etc.

Open issues

Does it work? How about the EL-stuff in Seam 2 templates, is it all mappable to FormattingRules? How about usage of the rendered attribute? Any other stuff? Your feedback is appreciated!

Contexts in JSR-299 is so important that it actually was incorporated in the name after a few attempts so let's have a look at what the contexts and scopes really are. We'll be using Weld as the reference when we want to get into implementation details but the core stuff is implementation agnostic and follow the rules of the specification. This blog posting is sort of a "scopes and contexts for dummies" with some simplifications, for a more formal approach you might want to Read The Fine Specification.

Contexts

A context is like a labeled shelf. There are shelves named @RequestScoped, @SessionScoped etc. When you use a bean of a particular scope, a bean instance is fetched from that shelf (or one is created and placed there if it wasn't already there) so that you are guaranteed to get the same instance the next time you reference it (withing the same lifecycle)

As a curiosity can be mentioned that there can actually be many shelves for a particular scope (as long as only one is active at any given time). 5 minutes of fame and honor to the one that can find a usecase for that. I think it has something to do with child activites that once was a part of the spec.

Scopes

The scope is the link between a bean and a context. Different contexts/scopes have different lifecycles and that determines for how long your stuff should be kept on the shelf. The shelf is automatically wiped clean when the scope hits it's end-of-life (e.g. the contents of the session context is destroyed when the session expires etc). There is no cheating, in Seam you could place almost any bean in any scope but in CDI, a bean of a particular scope always end up on the corresponding shelf. A bean has one and only one scope.

Proxies

Proxies are wrappers. There are different kinds of proxies, there are placeholder proxies used at injection points for normal scoped beans that look up the real instance before invoking methods on it and there are proxies that wrap interceptors and stuff around the real instance of the bean class.

Behind the scenes

So what really happens when you have something like this

@Model
public class Foo
{
   @Inject Bar bar;

   public String getPong()
   {
       return bar.pong();
   }
}

and

@SessionScoped
public class Bar implements Serializable
{
    public String pong()
    {
        return "pong";
    };
}

is that Weld at boot time creates the beans for both Foo and Bar. Foo is a @Named @RequestScoped bean (@Model stereotype) and Bar is a @SessionScoped one. There are no actual instances created at this time but there is a proxy injected at

@Inject Bar bar;

which has the ability to look up the Bar instance when needed. At this point both the request context and session contexts are empty. Now for the magic. When you in a xhtml page do something like

#{foo.pong}

the EL-resolver kicks in and comes to the conclusion that we need the named (EL-available) bean foo as a starting point. The BeanManager will resolve the bean and notice that Foo is @RequestScoped so it will ask the RequestContext for an instance. Since the request context is empty, an instance is generated, using the Foo-bean as template. This instance is placed in the request context in case someone needs it during the same request.

The getPong() method on that instance is called which hits the proxy for Bar. Since Bar is @SessionScoped, the BeanManager consults the session context for an instance. Since there is none there, one is created, and stuck in the session context for later reference. This instance is the one that has it's pong() method invoked.

When the request is over, the request context is destroyed and any destructors of Foo are called but since the session context is still alive (the session didn't terminate), the instance for Bar stays alive.

Now another request from the same webapp comes along. The request scope is once again empty and we have a new instance for foo but this time bar is already found in the session context and we get the same instance of Bar. Just as we should since we have a new request (fresh request context) but same 'ol session (old session context). In this way we can mix injection between contexts - we can inject @RequestScoped beans in @SessionScoped ones and vice versa and the proxies will make sure the correct instances are hit (or created) in the contexts.

Context not active

All contexts are not active all the time. Getting a context not active exception from an @ApplicationScoped bean is a bit tricky but the request context is only available when there is an active HTTP request and the session context is only available when there is an active HTTP session. It means that accessing beans in those contexts are no-go from e.g. MDB:s. Why is this?

Weld-specific stuff ahead, using the request context as an example: There is only one request context. Really. All 5000 concurrent users of your webapp share the same request context instance. Yup, the actual context data is stored in a ThreadLocal<BeanStore> field which means that (lucky for us) each users thread has own data in the field.

BeanStore is an interface and the particular implementation of it that is stuck in the ThreadLocal field of the request context is wrapping a HTTP servlet request. This means that adding stuff to that bean store actually places it in the request and clearing the bean store removes all keys from the request that belong to that bean store. The request attributes names are filtered through a naming scheme so that attributes that start with the @RequestScoped class name are considered belonging to that bean store.

What this means in practice is that the servlet request has to be there in order to work as backing storage for the bean store in the request context. The active-flag of the context is just there to indicate that at this point, Weld has populated that context with a true bean store.

So actually, you can't put stuff directly on the @RequestScoped shelf. You are only allowed to put things in a box (the servlet request backed bean store) Weld has placed on the shelf. The placing of the box is coupled to the activation of the context and the removal of the box is coupled to the deactivation. These activations and deactivations are handled by servlet request listeners that watch for the creation and destruction of the request.

When we're talking request scope, the context is short-lived and a new box is placed on the shelf when the request begins and it is cleared out when it ends but how about session scope? The story is very similar with the exception that the stuff belonging to the session bean store is not destroyed when the request ends. When the next request comes in, the session context is re-populated with a bean store that wraps the HTTP session. Since the previous request also used the HTTP session as backing storage, stuff placed in the session context then lives on in session attributes starting with the @SessionScoped class name. As you notice, for both request and session context, naming plays an important role. There are other things floating around the HTTP request and session attributes and we don't want to hit anything not belonging to us when we e.g. clear out a bean store.

It is important to realize that the limitations are not that of Welds implementation. Sure, there could be other ways of implementing the contexts that would make them appear available all the time but they could per definition not be semantically correct. You could also implement stuff without writing directly e.g. to the underlying HTTP session but when it comes to session replication, it's a Good Thing.

As a curiosity can be mentioned that due to the fact that the specification say the session context must survive session invalidation (which is not a Good Thing when you have direct HTTP session backed beanstore and try to access it after invalidation) there is a sort of buffering built into the session context that loads on init and caches stuff if the session should go AWOL at some point.

Honey, we need to have a conversation

The conversation context is actually like a named sub-session-context. The @RequestScoped shelf can have only one box where stuff is stored in but on the @ConversationScoped shelf there can be many boxes. The labels on the boxes is the conversation ID. Conversations can be either transient (the default) and in this case it behaves much like the request context and only lives during one HTTP request. This is not very useful, therefore conversations can be promoted to non-transient (long-running). This is achieved by calling the begin() or begin(String) methods on the Conversation bean that's available for injection. When the request ends, the Conversation Manager decides the fate of the conversation context by looking at the transient flag of the Conversation. If it is transient, the conversation context is destroyed, if it's non-transient it's left alone and the generated (or assigned) conversation ID is passed along as a HTTP parameter to the next request.

The Conversation Manager is also the one which determines if we should start with an empty, transient conversation and conversation context or if we should resume an existing one when a request comes in. The cid parameter is looked for in the incoming HTTP request. No parameter present means transient conversation but if there is a value for cid, say 1, the conversation manger picks down the box labeled 1 from it's storage and loads it into the conversation context. Well, actually it wraps the HTTP session with a conversation-beanstore suited to match that conversation id (again, naming play an important role) and pre-populates the Conversation instance with the cid and transient flagged to false. We are free to switch conversations, promoting and demoting them and the HTTP session will mirror those contexts through its attributes, suited to match the names of the conversations.

If you think you can get cute and do some URL-rewriting by modify the passed-along cid-parameter feel free. Since your own session is used as backing storage, the only thing you can do is switch to another conversation of your own (or bomb out because the cid was not known). There is no way you can access conversational data from another user, even the specification says you can't cross the HTTP session boundary.

As a curiosity could be mentioned that it's harder and harder with modern browsers to actually get new sessions since tabbed browsings etc have brought along the side-effect that the session and cookies are shared among browser instances. Earlier it was usually enough to start a new browser but nowadays you usually have to use incognito/private browsing to test the session boundaries with the same brand of browser.

Conclusion

Hopefully you are now confused at a higher level. You might also want to check out the specification on how to write custom contexts and scope types. You might also want to have a look at the Weld source code in the org.jboss.weld.context package and look at how the BeanManagerImpl does the resolving.

In this shortish part, we'll add some interfaces to our application so external users can read the current greetings. Expanding the interfaces so greetings can be added is left as an exercise for the reader

JAX-RS

REST is hip (and now in EE 6 as JAX-RS) so let's throw it in. Add

package com.acme.greetings;

import java.util.List;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

@Path(value = "/RESTExport")
public class RESTExporter
{

   @Inject
   GreetingServer greetingServer;

   @GET
   @Path("/greetings")
   @Produces("application/xml")
   public List<Greeting> getGreetings()
   {
      return greetingServer.getGreetings();
   }

}

That fetches the current greetings (notice the injection) from the server and presents them in XML format. To hook up the JAX-RS implementation, RESTEasy, add the following to web.xml

<context-param>
	<param-name>resteasy.scan</param-name>
	<param-value>true</param-value>
</context-param>
<context-param>
	<param-name>resteasy.servlet.mapping.prefix</param-name>
	<param-value>/resteasy</param-value>
</context-param>
<listener>
	<listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class>
</listener>
<servlet>
	<servlet-name>Resteasy</servlet-name>
	<servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>Resteasy</servlet-name>
	<url-pattern>/resteasy/*</url-pattern>
</servlet-mapping>

and the @XMLRootElement annotation to the top of our Greeting class

@Entity
@Audited
@XmlRootElement
public class Greeting

Your greetings should now be available from /Greetings/resteasy/RESTExport/greetings.

JAX-WS

Adding a Web Service is even more simple. Add

package com.acme.greetings;

import java.util.List;

import javax.inject.Inject;
import javax.jws.WebService;

@WebService
public class WebServiceExporter
{
   @Inject
   GreetingServer greetingServer;

   public List<Greeting> getGreetings()
   {
      return greetingServer.getGreetings();
   }

}

That does a similar job as our RESTExporter and then hook it up in web.xml

<servlet>
	<servlet-name>WSExport</servlet-name>
	<servlet-class>com.acme.greetings.WebServiceExporter</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>WSExport</servlet-name>
	<url-pattern>/WebServiceExport/*</url-pattern>
</servlet-mapping>

Hmm. Wonder if you can make it auto-register? Anyway, the WDSL should be viewable from /Greetings/WebServiceExport?wsdl

Conclusion

This was a short one. Partly because setting things up is really straightforward and don't require us to do that many workarounds. Hopefully, once Aslak finishes the Arqullian DBUnit integration (I already heard rumors on JSFUnit integration) I can be back with a more thorough article on testing all parts of the application.

My wife has delivered our daughter and JBoss has delivered a snapshot of the AS 6 that can do most of the stuff we set out to do, so let's move on!

The good, the bad and our daily workarounds

So, since we're living on the edge here, head over to JBoss Hudson and grab the latest successful build (#1750 or later). Install the envers.jar in the server and change the http listener port like we did in the end of part II. Remember to update your JBOSS_HOME environment variable if you use one.

There is a slight regression with the auto-registration of the faces servlet so add this to your web.xml

<servlet>
	<servlet-name>Faces Servlet</servlet-name>
	<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
	<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
	<servlet-name>Faces Servlet</servlet-name>
	<url-pattern>/faces/*</url-pattern>
</servlet-mapping>

The ZipException is gone so clear out your faces-config.xml from the merged ICEfaces stuff and delete the icefaces directory from your local repository so you can pick up fresh copies of the original jars.

There is also another regression that has something to do with redirects and the ICEfaces PushRenderer (perhaps). If you go straight to the /Greetings context root, the PushRenderer is not that pushy, you'll have to use the full /Greetings/faces/greetings.xhtml for now.

Look mama, no EARs

We're going EJB and let's do some refactorings while we're at it. Remove the GreetingBean and create the following classes

package com.acme.greetings;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;

import javax.annotation.PostConstruct;
import javax.ejb.Lock;
import javax.ejb.LockType;
import javax.ejb.Stateful;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Event;
import javax.inject.Inject;
import javax.inject.Named;

import org.icefaces.application.PushRenderer;

@Stateful
@ApplicationScoped
@Named
public class GreetingServer implements Serializable
{
   private static final long serialVersionUID = 1L;

   List<Greeting> greetings = new ArrayList<Greeting>();
   
   @Inject
   @Added
   Event<Greeting> greetingAddedEvent;

   @Inject
   GreetingArchiver greetingArchiver;
   
   @Inject
   GreetingEcho greetingEcho;
   
   @PostConstruct
   public void init()
   {
      greetings = greetingArchiver.loadGreetings();
   }

   @Lock(LockType.WRITE)
   public void addGreeting(Greeting greeting)
   {
      greetings.add(greeting);
      PushRenderer.render("greetings");
      greetingAddedEvent.fire(greeting);
      Future<Boolean> result = greetingEcho.echo(greeting);
   }

   @Lock(LockType.READ)
   public List<Greeting> getGreetings()
   {
      return greetings;
   }
}

and

package com.acme.greetings;

import javax.annotation.PostConstruct;
import javax.ejb.Stateful;
import javax.enterprise.inject.Model;
import javax.inject.Inject;

import org.icefaces.application.PushRenderer;

import com.icesoft.faces.context.effects.Appear;
import com.icesoft.faces.context.effects.Effect;

@Stateful
@Model
public class GreetingClient
{
   Greeting greeting = new Greeting();

   @Inject
   GreetingServer greetingServer;

   @PostConstruct
   public void init()
   {
      PushRenderer.addCurrentSession("greetings");
   }
   
   public Effect getAppear()
   {
      return new Appear();
   }

   public Greeting getGreeting()
   {
      return greeting;
   }

   public void setGreeting(Greeting greeting)
   {
      this.greeting = greeting;
   }

   public void addGreeting()
   {
      greetingServer.addGreeting(greeting);
   }
}

The GreetingServer keeps the greetings and the client has injected the server and interacts with it (we've thrown in some annotations for concurrency control to the greeting list). You might notice we also have a new injection

@Inject
GreetingEcho greetingEcho;

which is called like

Future<Boolean> result = greetingEcho.echo(greeting);

This is an asynchronous call to GreetingEcho that looks like

package com.acme.greetings;

import java.util.concurrent.Future;

import javax.ejb.AsyncResult;
import javax.ejb.Asynchronous;
import javax.ejb.Stateless;

@Stateless
public class GreetingEcho
{

   @Asynchronous
   public Future<Boolean> echo(Greeting greeting)
   {
      System.out.println(String.format("Got a new greeting: %s", greeting.getText()));
      return new AsyncResult<Boolean>(true);
   }
}

This is kind of funny EE 6 construct since it really exits the @Asynchronous method immedeately, returning a handle to the task. Well, good luck trying to cancel that output before it finished. This is kind of JMS-light. And yes, it's obviously used just for show-off here.

You'll also notice that our DB bean has gotten a new look

package com.acme.greetings;

import java.util.Date;
import java.util.List;

import javax.ejb.Stateless;
import javax.enterprise.event.Observes;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.ParameterExpression;
import javax.persistence.criteria.Root;

@Stateless
public class GreetingArchiver
{
   @Inject
   @GreetingDB
   EntityManager db;

   CriteriaQuery<Greeting> loadQuery;
   ParameterExpression<Date> timestampParam;

   @Inject
   public void initQuery(@GreetingDB EntityManagerFactory emf)
   {
      CriteriaBuilder cb = emf.getCriteriaBuilder();
      timestampParam = cb.parameter(Date.class);
      loadQuery = cb.createQuery(Greeting.class);
      Root<Greeting> greeting = loadQuery.from(Greeting.class);
      loadQuery.select(greeting);
      loadQuery.where(cb.greaterThan(greeting.get(Greeting_.created), timestampParam));
   }

   public void saveGreeting(@Observes @Added Greeting greeting)
   {
      db.persist(greeting);
   }

   public List<Greeting> loadGreetings()
   {
      Date tenMinutesAgo = new Date();
      tenMinutesAgo.setTime(tenMinutesAgo.getTime() - 10 * 60 * 1000);
      return db.createQuery(loadQuery).setParameter(timestampParam, tenMinutesAgo).getResultList();
   }
}

The persistence has been much simplified thanks to CMT.

And finally the view to use them

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:ice="http://www.icesoft.com/icefaces/component"
	xmlns:ui="http://java.sun.com/jsf/facelets">
	<h:head>
		<title>
			Greetings
		</title>
	</h:head>

	<h:body>
		<h:form>
			<ice:inputText id="feedback" value="#{greetingClient.greeting.text}" effect="#{greetingClient.appear}"/>
			<h:message for="feedback" />
			<h:commandButton value="Add" action="#{greetingClient.addGreeting}" />
			<h:dataTable value="#{greetingServer.greetings}" var="greeting">
				<h:column>
					<h:outputText value="#{greeting.text}"/>
				</h:column>
			</h:dataTable>
		</h:form>
	</h:body>
</html>

JMS and JavaMail

Speaking of JMS, let's add a MDB

package com.acme.greetings;

import javax.annotation.Resource;
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

@MessageDriven(activationConfig = { @ActivationConfigProperty(propertyName = "destination", propertyValue = "topic/GreetingTopic"), @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Topic") })
public class GreetingMailer implements MessageListener
{
   @Resource(mappedName = "java:/Mail")
   Session mailSession;

   @Override
   public void onMessage(Message message)
   {
      TextMessage textMessage = (TextMessage) message;
      try
      {
         mail(textMessage.getText());
      }
      catch (Exception e)
      {
         // Forgive me, for I have sinned
         System.out.println(String.format("No mailing: %s", e.getMessage()));
      }
   }

   private void mail(String greeting) throws MessagingException
   {
      MimeMessage message = new MimeMessage(mailSession);
      message.setFrom(new InternetAddress("greetingmaster@nowhere.org"));
      message.addRecipient(javax.mail.Message.RecipientType.TO, new InternetAddress("you@yourplace.com"));
      message.setSubject("New greeting has arrived!");
      message.setText(greeting);
      Transport.send(message);
   }
}

This is a message driven bean that listens to the topic /topic/GreetingTopic and when a message is recieved, it grabs the default Mail session from the application server and send off a notification, configure it in the mail-service.xml in the deploy directory. You should also add the topic to the deploy/hornetq/hornetq-jms.xml like

<topic name="GreetingTopic">
   <entry name="/topic/GreetingTopic"/>
</topic>

There are probably better ways of deploying topics but I'm not writing a JBoss AS tutorial here. This MDB isn't going to see any action unless someone actually send it messages. Let's add a

package com.acme.greetings;

import javax.annotation.Resource;
import javax.ejb.Stateless;
import javax.enterprise.event.Observes;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;

@Stateless
public class JMSDispatcher
{
   @Resource(mappedName = "/ConnectionFactory")
   ConnectionFactory connectionFactory;

   @Resource(mappedName = "/topic/GreetingTopic")
   Topic topic;

   public void broadcast(@Observes @Added Greeting greeting) throws JMSException
   {
      Session session = null;
      MessageProducer producer = null;
      Connection connection = null;
      try
      {
         connection = connectionFactory.createConnection();
         session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
         producer = session.createProducer(topic);
         TextMessage message = session.createTextMessage(greeting.getText());
         producer.send(message);
      }
      finally
      {
         if (producer != null)
         {
            producer.close();
         }
         if (session != null)
         {
            session.close();
         }
         if (connection != null)
         {
            connection.close();
         }

      }
   }
}

This bean observers the same added greeting as the GreetingArchiver and sends the greeting of to our topic. There are probably better ways of writing this but I'm not writing a JMS tutorial (either). Hmm, I forget what kind of tutorial I'm actually writing.

Something for the future

This snapshot doesn't have everyting yet, I'd like to have @Scheduled greetings so we don't get lonely. This doesn't actually work yet, but let's add it and keep upgrading our snapshots so perhaps one day we'll get lucky.

package com.acme.greetings;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.ejb.Schedule;
import javax.ejb.Singleton;
import javax.inject.Inject;

@Singleton
public class ArbiBean
{
   static final List<String> comments = new ArrayList<String>()
   {
      private static final long serialVersionUID = 1L;
      {
         add("Tried the new JRebel? I heard it can read your thoughts?");
         add("Where can I find the specs?");
         add("Shouldn't you be using RichFaces 4 instead?");
         add("How is the Seam 3 performance compared to Seam 2?");
         add("PIGBOMB!");
      }
   };

   @Inject
   GreetingServer greetingServer;

   @Schedule(second = "*/15")
   public void comment()
   {
      greetingServer.addGreeting(new Greeting(getRandomComment()));
   }

   private String getRandomComment()
   {
      return comments.get(new Random(comments.size()).nextInt());
   }
}

This one should start up and send comments to the server every 15 seconds. But like I said, not yet.

Conclusion

This ends part IV. In part V we'll add JAX-RS and JAX-WS to share our greetings with the world. If you haven't noticed it by now, developing against snapshots and alpha-releases require a fair amount of workarounds. But as a developer you can choose - do you want to sit around and wait for the final product or do you want to join the round and get first-hand knowledge on things to come (filing JIRAs as you encounter issues).

Showing 1 to 5 of 8 blog entries