SeamFramework.orgCommunity Documentation
The two core concepts in Seam are the notion of a context and the notion of a component. Components are stateful objects, usually EJBs, and an instance of a component is associated with a context, and given a name in that context. Bijection provides a mechanism for aliasing internal component names (instance variables) to contextual names, allowing component trees to be dynamically assembled, and reassembled by Seam.
Let's start by describing the contexts built in to Seam.
Seam contexts are created and destroyed by the framework. The application does not control context demarcation via explicit Java API calls. Context are usually implicit. In some cases, however, contexts are demarcated via annotations.
The basic Seam contexts are:
Stateless context
Event (i.e., request) context
Page context
Conversation context
Session context
Business process context
Application context
You will recognize some of these contexts from servlet and related specifications. However, two of them might be new to you: conversation context, and business process context. One reason state management in web applications is so fragile and error-prone is that the three built-in contexts (request, session and application) are not especially meaningful from the point of view of the business logic. A user login session, for example, is a fairly arbitrary construct in terms of the actual application work flow. Therefore, most Seam components are scoped to the conversation or business process contexts, since they are the contexts which are most meaningful in terms of the application.
Let's look at each context in turn.
Components which are truly stateless (stateless session beans, primarily) always live in the stateless context (which is basically the absence of a context since the instance Seam resolves is not stored). Stateless components are not very interesting, and are arguably not very object-oriented. Nevertheless, they do get developed and used and are thus an important part of any Seam application.
The event context is the "narrowest" stateful context, and is a generalization of the notion of the web request context to cover other kinds of events. Nevertheless, the event context associated with the lifecycle of a JSF request is the most important example of an event context, and the one you will work with most often. Components associated with the event context are destroyed at the end of the request, but their state is available and well-defined for at least the lifecycle of the request.
When you invoke a Seam component via RMI, or Seam Remoting, the event context is created and destroyed just for the invocation.
The page context allows you to associate state with a particular instance of a rendered page. You can initialize state in your event listener, or while actually rendering the page, and then have access to it from any event that originates from that page. This is especially useful for functionality like clickable lists, where the list is backed by changing data on the server side. The state is actually serialized to the client, so this construct is extremely robust with respect to multi-window operation and the back button.
The conversation context is a truly central concept in Seam. A conversation is a unit of work from the point of view of the user. It might span several interactions with the user, several requests, and several database transactions. But to the user, a conversation solves a single problem. For example, "book hotel", "approve contract", "create order" are all conversations. You might like to think of a conversation implementing a single "use case" or "user story", but the relationship is not necessarily quite exact.
A conversation holds state associated with "what the user is doing now, in this window". A single user may have multiple conversations in progress at any point in time, usually in multiple windows. The conversation context allows us to ensure that state from the different conversations does not collide and cause bugs.
It might take you some time to get used to thinking of applications in terms of conversations. But once you get used to it, we think you'll love the notion, and never be able to not think in terms of conversations again!
Some conversations last for just a single request. Conversations that span multiple requests must be demarcated using annotations provided by Seam.
Some conversations are also tasks. A task is a conversation that is significant in terms of a long-running business process, and has the potential to trigger a business process state transition when it is successfully completed. Seam provides a special set of annotations for task demarcation.
Conversations may be nested, with one conversation taking place "inside" a wider conversation. This is an advanced feature.
Usually, conversation state is actually held by Seam in the servlet session between requests. Seam implements configurable conversation timeout, automatically destroying inactive conversations, and thus ensuring that the state held by a single user login session does not grow without bound if the user abandons conversations.
Seam serializes processing of concurrent requests that take place in the same long-running conversation context, in the same process.
Alternatively, Seam may be configured to keep conversational state in the client browser.
A session context holds state associated with the user login session. While there are some cases where it is useful to share state between several conversations, we generally frown on the use of session context for holding components other than global information about the logged in user.
In a JSR-168 portal environment, the session context represents the portlet session.
The business process context holds state associated with the long running business process. This state is managed and made persistent by the BPM engine (JBoss jBPM). The business process spans multiple interactions with multiple users, so this state is shared between multiple users, but in a well-defined manner. The current task determines the current business process instance, and the lifecycle of the business process is defined externally using a process definition language, so there are no special annotations for business process demarcation.
The application context is the familiar servlet context from the servlet spec. Application context is mainly useful for holding static information such as configuration data, reference data or metamodels. For example, Seam stores its own configuration and metamodel in the application context.
A context defines a namespace, a set of context variables. These work much the same as session or request attributes in the servlet spec. You may bind any value you like to a context variable, but usually we bind Seam component instances to context variables.
So, within a context, a component instance is identified by the context variable name (this is
usually, but not always, the same as the component name). You may programmatically access a named
component instance in a particular scope via the Contexts
class, which provides
access to several thread-bound instances of the Context
interface:
User user = (User) Contexts.getSessionContext().get("user");
You may also set or change the value associated with a name:
Contexts.getSessionContext().set("user", user);
Usually, however, we obtain components from a context via injection, and put component instances into a context via outjection.
Sometimes, as above, component instances are obtained from a particular known scope. Other times, all stateful scopes are searched, in priority order. The order is as follows:
Event context
Page context
Conversation context
Session context
Business process context
Application context
You can perform a priority search by calling Contexts.lookupInStatefulContexts()
.
Whenever you access a component by name from a JSF page, a priority search occurs.
Neither the servlet nor EJB specifications define any facilities for managing concurrent requests originating from the same client. The servlet container simply lets all threads run concurrently and leaves enforcing thread safeness to application code. The EJB container allows stateless components to be accessed concurrently, and throws an exception if multiple threads access a stateful session bean.
This behavior might have been okay in old-style web applications which were based around fine-grained, synchronous requests. But for modern applications which make heavy use of many fine-grained, asynchronous (AJAX) requests, concurrency is a fact of life, and must be supported by the programming model. Seam weaves a concurrency management layer into its context model.
The Seam session and application contexts are multithreaded. Seam will allow concurrent requests in a context to be processed concurrently. The event and page contexts are by nature single threaded. The business process context is strictly speaking multi-threaded, but in practice concurrency is sufficiently rare that this fact may be disregarded most of the time. Finally, Seam enforces a single thread per conversation per process model for the conversation context by serializing concurrent requests in the same long-running conversation context.
Since the session context is multithreaded, and often contains volatile state, session scope
components are always protected by Seam from concurrent access so long as the Seam interceptors
are not disabled for that component. If interceptors are disabled, then any thread-safety that is
required must be implemented by the component itself. Seam serializes requests to session
scope JavaBeans by default (and detects and breaks any deadlocks that occur). This is
not the default behaviour for application scoped components however, since application scoped components
do not usually hold volatile state and because synchronization at the global level is
extremely expensive. However, you can force a serialized threading model on any
JavaBean component by adding the @Synchronized
annotation.
Seam 2.3 removed the serialization of Stateful session beans by Seam synchronization interceptor because stateful session beans are serialized by EJB 3.1 container by default .
This concurrency model means that AJAX clients can safely use volatile session and conversational state, without the need for any special work on the part of the developer.
Be warned that Statefull session Beans are not serialized by Seam anymore. Serialization of Statefull session beans are controlled by EJB container, so there is no need for Seam to duplicate that. So @Synchronized annotation is ignored on Statefull session beans.
Seam components are POJOs (Plain Old Java Objects). In particular, they are JavaBeans or EJB 3.0 enterprise beans. While Seam does not require that components be EJBs and can even be used without an EJB 3.0 compliant container, Seam was designed with EJB 3.0 in mind and includes deep integration with EJB 3.0. Seam supports the following component types.
EJB 3.0 stateless session beans
EJB 3.0 stateful session beans
EJB 3.0 entity beans (i.e., JPA entity classes)
JavaBeans
EJB 3.0 message-driven beans
Spring beans (see Chapter 28, Spring Framework integration)
Stateless session bean components are not able to hold state across multiple invocations. Therefore, they usually work by operating upon the state of other components in the various Seam contexts. They may be used as JSF action listeners, but cannot provide properties to JSF components for display.
Stateless session beans always live in the stateless context.
Stateless session beans can be accessed concurrently as a new instance is used for each request. Assigning the instance to the request is the responsibility of the EJB3 container (normally instances will be allocated from a reusable pool meaning that you may find any instance variables contain data from previous uses of the bean).
Stateless session beans are the least interesting kind of Seam component.
Seam stateless session bean components may be instantiated using Component.getInstance()
or @In(create=true)
. They should not be directly instantiated via JNDI lookup
or the new
operator.
Stateful session bean components are able to hold state not only across multiple invocations of the
bean, but also across multiple requests. Application state that does not belong in the database should
usually be held by stateful session beans. This is a major difference between Seam and many other web
application frameworks. Instead of sticking information about the current conversation directly in the
HttpSession
, you should keep it in instance variables of a stateful session bean
that is bound to the conversation context. This allows Seam to manage the lifecycle of this state for
you, and ensure that there are no collisions between state relating to different concurrent
conversations.
Stateful session beans are often used as JSF action listener, and as backing beans that provide properties to JSF components for display or form submission.
By default, stateful session beans are bound to the conversation context. They may never be bound to the page or stateless contexts.
Concurrent requests to session-scoped stateful session beans are not serialized by Seam as long as EJB 3.1 has changed that. This is a difference in comparison to previous Seam 2.2.x.
Seam stateful session bean components may be instantiated using Component.getInstance()
or @In(create=true)
. They should not be directly instantiated via JNDI lookup
or the new
operator.
Entity beans may be bound to a context variable and function as a seam component. Because entities have a persistent identity in addition to their contextual identity, entity instances are usually bound explicitly in Java code, rather than being instantiated implicitly by Seam.
Entity bean components do not support bijection or context demarcation. Nor does invocation of an entity bean trigger validation.
Entity beans are not usually used as JSF action listeners, but do often function as backing beans that provide properties to JSF components for display or form submission. In particular, it is common to use an entity as a backing bean, together with a stateless session bean action listener to implement create/update/delete type functionality.
By default, entity beans are bound to the conversation context. They may never be bound to the stateless context.
Note that it in a clustered environment is somewhat less efficient to bind an entity bean directly to a conversation or session scoped Seam context variable than it would be to hold a reference to the entity bean in a stateful session bean. For this reason, not all Seam applications define entity beans to be Seam components.
Seam entity bean components may be instantiated using Component.getInstance()
,
@In(create=true)
or directly using the new
operator.
JavaBeans may be used just like a stateless or stateful session bean. However, they do not provide the functionality of a session bean (declarative transaction demarcation, declarative security, efficient clustered state replication, EJB 3.0 persistence, timeout methods, etc).
In a later chapter, we show you how to use Seam and Hibernate without an EJB container. In this use case, components are JavaBeans instead of session beans. Note, however, that in many application servers it is somewhat less efficient to cluster conversation or session scoped Seam JavaBean components than it is to cluster stateful session bean components.
By default, JavaBeans are bound to the event context.
Concurrent requests to session-scoped JavaBeans are always serialized by Seam.
Seam JavaBean components may be instantiated using Component.getInstance()
or @In(create=true)
. They should not be directly instantiated using the
new
operator.
Message-driven beans may function as a seam component. However, message-driven beans are called quite differently to other Seam components - instead of invoking them via the context variable, they listen for messages sent to a JMS queue or topic.
Message-driven beans may not be bound to a Seam context. Nor do they have access to the session or conversation state of their "caller". However, they do support bijection and some other Seam functionality.
Message-driven beans are never instantiated by the application. They are instantiated by the EJB container when a message is received.
In order to perform its magic (bijection, context demarcation, validation, etc), Seam must intercept component invocations. For JavaBeans, Seam is in full control of instantiation of the component, and no special configuration is needed. For entity beans, interception is not required since bijection and context demarcation are not defined. For session beans, we must register an EJB interceptor for the session bean component. We could use an annotation, as follows:
@Stateless
@Interceptors(SeamInterceptor.class)
public class LoginAction implements Login {
...
}
But a much better way is to define the interceptor in ejb-jar.xml
.
<interceptors>
<interceptor>
<interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor>
</interceptors>
<assembly-descriptor>
<interceptor-binding>
<ejb-name>*</ejb-name>
<interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor-binding>
</assembly-descriptor>
All seam components need a name. We can assign a name to a component using the
@Name
annotation:
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
...
}
This name is the seam component name and is not related to any other name defined by the EJB specification. However, seam component names work just like JSF managed bean names and you can think of the two concepts as identical.
@Name
is not the only way to define a component name, but we always need
to specify the name somewhere. If we don't, then none of the other
Seam annotations will function.
Whenever Seam instantiates a component, it binds the new instance to a variable in the scope configured
for the component that matches the component name. This behavior is identical to how JSF managed beans
work, except that Seam allows you to configure this mapping using annotations rather than XML. You can
also programmatically bind a component to a context variable. This is useful if a particular component
serves more than one role in the system. For example, the currently logged in User
might be bound to the currentUser
session context variable, while a
User
that is the subject of some administration functionality might be bound to the
user
conversation context variable. Be careful, though, because through a
programmatic assignment, it's possible to overwrite a context variable that has a reference to a Seam
component, potentially confusing matters.
For very large applications, and for built-in seam components, qualified component names are often used to avoid naming conflicts.
@Name("com.jboss.myapp.loginAction")
@Stateless
public class LoginAction implements Login {
...
}
We may use the qualified component name both in Java code and in JSF's expression language:
<h:commandButton type="submit" value="Login"
action="#{com.jboss.myapp.loginAction.login}"/>
Since this is noisy, Seam also provides a means of aliasing a qualified name to a simple name. Add a
line like this to the components.xml
file:
<factory name="loginAction" scope="STATELESS" value="#{com.jboss.myapp.loginAction}"/>
All of the built-in Seam components have qualified names but can be accessed through
their unqualified names due to the namespace import feature of Seam.
The components.xml
file included in the Seam JAR defines the following
namespaces.
<components xmlns="http://jboss.org/schema/seam/components">
<import>org.jboss.seam.core</import>
<import>org.jboss.seam.cache</import>
<import>org.jboss.seam.transaction</import>
<import>org.jboss.seam.framework</import>
<import>org.jboss.seam.web</import>
<import>org.jboss.seam.faces</import>
<import>org.jboss.seam.international</import>
<import>org.jboss.seam.theme</import>
<import>org.jboss.seam.pageflow</import>
<import>org.jboss.seam.bpm</import>
<import>org.jboss.seam.jms</import>
<import>org.jboss.seam.mail</import>
<import>org.jboss.seam.security</import>
<import>org.jboss.seam.security.management</import>
<import>org.jboss.seam.security.permission</import>
<import>org.jboss.seam.captcha</import>
<import>org.jboss.seam.excel.exporter</import>
<!-- ... --->
</components>
When attempting to resolve an unqualified name, Seam will check each of those namespaces, in order.
You can include additional namespaces in your application's components.xml
file
for application-specific namespaces.
We can override the default scope (context) of a component using the @Scope
annotation. This lets us define what context a component instance is bound to, when it is instantiated
by Seam.
@Name("user")
@Entity
@Scope(SESSION)
public class User {
...
}
org.jboss.seam.ScopeType
defines an enumeration of possible scopes.
Some Seam component classes can fulfill more than one role in the system. For example, we often have
a User
class which is usually used as a session-scoped component representing the
current user but is used in user administration screens as a conversation-scoped component. The
@Role
annotation lets us define an additional named role for a component, with a
different scope — it lets us bind the same component class to different context variables. (Any
Seam component instance may be bound to multiple context variables, but this lets
us do it at the class level, and take advantage of auto-instantiation.)
@Name("user")
@Entity
@Scope(CONVERSATION)
@Role(name="currentUser", scope=SESSION)
public class User {
...
}
The @Roles
annotation lets us specify as many additional roles as we like.
@Name("user")
@Entity
@Scope(CONVERSATION)
@Roles({@Role(name="currentUser", scope=SESSION),
@Role(name="tempUser", scope=EVENT)})
public class User {
...
}
Like many good frameworks, Seam eats its own dogfood and is implemented mostly as a set of built-in
Seam interceptors (see later) and Seam components. This makes it easy for applications to interact with
built-in components at runtime or even customize the basic functionality of Seam by replacing the
built-in components with custom implementations. The built-in components are defined in the Seam
namespace org.jboss.seam.core
and the Java package of the same name.
The built-in components may be injected, just like any Seam components, but they also provide
convenient static instance()
methods:
FacesMessages.instance().add("Welcome back, #{user.name}!");
Dependency injection or inversion of control is by now a familiar concept to most Java developers. Dependency injection allows a component to obtain a reference to another component by having the container "inject" the other component to a setter method or instance variable. In all dependency injection implementations that we have seen, injection occurs when the component is constructed, and the reference does not subsequently change for the lifetime of the component instance. For stateless components, this is reasonable. From the point of view of a client, all instances of a particular stateless component are interchangeable. On the other hand, Seam emphasizes the use of stateful components. So traditional dependency injection is no longer a very useful construct. Seam introduces the notion of bijection as a generalization of injection. In contrast to injection, bijection is:
contextual - bijection is used to assemble stateful components from various different contexts (a component from a "wider" context may even have a reference to a component from a "narrower" context)
bidirectional - values are injected from context variables into attributes of the component being invoked, and also outjected from the component attributes back out to the context, allowing the component being invoked to manipulate the values of contextual variables simply by setting its own instance variables
dynamic - since the value of contextual variables changes over time, and since Seam components are stateful, bijection takes place every time a component is invoked
In essence, bijection lets you alias a context variable to a component instance variable, by specifying that the value of the instance variable is injected, outjected, or both. Of course, we use annotations to enable bijection.
The @In
annotation specifies that a value should be injected, either into an instance
variable:
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
@In User user;
...
}
or into a setter method:
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
User user;
@In
public void setUser(User user) {
this.user=user;
}
...
}
By default, Seam will do a priority search of all contexts, using the name of the property or instance
variable that is being injected. You may wish to specify the context variable name explicitly, using, for
example, @In("currentUser")
.
If you want Seam to create an instance of the component when there is no existing component instance
bound to the named context variable, you should specify @In(create=true)
. If the value is
optional (it can be null), specify @In(required=false)
.
For some components, it can be repetitive to have to specify @In(create=true)
everywhere
they are used. In such cases, you can annotate the component @AutoCreate
, and then it
will always be created, whenever needed, even without the explicit use of create=true
.
You can even inject the value of an expression:
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
@In("#{user.username}") String username;
...
}
Injected values are disinjected (i.e., set to null
) immediately after method
completion and outjection.
(There is much more information about component lifecycle and injection in the next chapter.)
The @Out
annotation specifies that an attribute should be outjected, either from an
instance variable:
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
@Out User user;
...
}
or from a getter method:
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
User user;
@Out
public User getUser() {
return user;
}
...
}
An attribute may be both injected and outjected:
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
@In @Out User user;
...
}
or:
@Name("loginAction")
@Stateless
public class LoginAction implements Login {
User user;
@In
public void setUser(User user) {
this.user=user;
}
@Out
public User getUser() {
return user;
}
...
}
Session bean and entity bean Seam components support all the usual EJB 3.0 lifecycle callback
(@PostConstruct
, @PreDestroy
, etc). But Seam also supports
the use of any of these callbacks with JavaBean components. However, since these annotations are
not available in a J2EE environment, Seam defines two additional component lifecycle callbacks,
equivalent to @PostConstruct
and @PreDestroy
.
The @Create
method is called after Seam instantiates a component.
Components may define only one @Create
method.
The @Destroy
method is called when the context that the Seam component is bound to
ends. Components may define only one @Destroy
method.
In addition, stateful session bean components must define a method with no parameters
annotated @Remove
. This method is called by Seam when the context ends.
Finally, a related annotation is the @Startup
annotation, which may be applied to any
application or session scoped component. The @Startup
annotation tells Seam to
instantiate the component immediately, when the context begins, instead of waiting until it is first
referenced by a client. It is possible to control the order of instantiation of startup components by
specifying @Startup(depends={....})
.
The @Install
annotation lets you control conditional installation of components that
are required in some deployment scenarios and not in others. This is useful if:
You want to mock out some infrastructural component in tests.
You want change the implementation of a component in certain deployment scenarios.
You want to install some components only if their dependencies are available (useful for framework authors).
@Install
works by letting you specify precedence
and dependencies.
The precedence of a component is a number that Seam uses to decide which component to install when there are multiple classes with the same component name in the classpath. Seam will choose the component with the higher precedence. There are some predefined precedence values (in ascending order):
BUILT_IN
— the lowest precedence components are
the components built in to Seam.
FRAMEWORK
— components defined by third-party
frameworks may override built-in components, but are overridden by
application components.
APPLICATION
— the default precedence. This is
appropriate for most application components.
DEPLOYMENT
— for application components which
are deployment-specific.
MOCK
— for mock objects used in testing.
Suppose we have a component named messageSender
that talks to
a JMS queue.
@Name("messageSender")
public class MessageSender {
public void sendMessage() {
//do something with JMS
}
}
In our unit tests, we don't have a JMS queue available, so we would like to stub out this method. We'll create a mock component that exists in the classpath when unit tests are running, but is never deployed with the application:
@Name("messageSender")
@Install(precedence=MOCK)
public class MockMessageSender extends MessageSender {
public void sendMessage() {
//do nothing!
}
}
The precedence
helps Seam decide which version to use when it finds
both components in the classpath.
This is nice if we are able to control exactly which classes are in the classpath. But
if I'm writing a reusable framework with many dependencies, I don't want to have to
break that framework across many jars. I want to be able to decide which components
to install depending upon what other components are installed, and upon what classes
are available in the classpath. The @Install
annotation also
controls this functionality. Seam uses this mechanism internally to enable conditional
installation of many of the built-in components. However, you probably won't need to
use it in your application.
Who is not totally fed up with seeing noisy code like this?
private static final Log log = LogFactory.getLog(CreateOrderAction.class);
public Order createOrder(User user, Product product, int quantity) {
if ( log.isDebugEnabled() ) {
log.debug("Creating new order for user: " + user.username() +
" product: " + product.name()
+ " quantity: " + quantity);
}
return new Order(user, product, quantity);
}
It is difficult to imagine how the code for a simple log message could possibly be more verbose. There is more lines of code tied up in logging than in the actual business logic! I remain totally astonished that the Java community has not come up with anything better in 10 years.
Seam provides a logging API that simplifies this code significantly:
@Logger private Log log;
public Order createOrder(User user, Product product, int quantity) {
log.debug("Creating new order for user: #0 product: #1 quantity: #2", user.username(), product.name(), quantity);
return new Order(user, product, quantity);
}
It doesn't matter if you declare the log
variable static or not — it will work
either way, except for entity bean components which require the log
variable to be
static.
Note that we don't need the noisy if ( log.isDebugEnabled() )
guard, since string
concatenation happens inside the debug()
method. Note also that we
don't usually need to specify the log category explicitly, since Seam knows what component it is injecting
the Log
into.
If User
and Product
are Seam components available in the current
contexts, it gets even better:
@Logger private Log log;
public Order createOrder(User user, Product product, int quantity) {
log.debug("Creating new order for user: #{user.username} product: #{product.name} quantity: #0", quantity);
return new Order(user, product, quantity);
}
Seam logging automagically chooses whether to send output to log4j or JDK logging. If log4j is in the classpath, Seam with use it. If it is not, Seam will use JDK logging.
Many application servers feature an amazingly broken implementation of HttpSession
clustering, where changes to the state of mutable objects bound to the session are only replicated when the
application calls setAttribute()
explicitly. This is a source of bugs that can not
effectively be tested for at development time, since they will only manifest when failover occurs.
Furthermore, the actual replication message contains the entire serialized object graph bound to the session
attribute, which is inefficient.
Of course, EJB stateful session beans must perform automatic dirty checking and replication of mutable state and a sophisticated EJB container can introduce optimizations such as attribute-level replication. Unfortunately, not all Seam users have the good fortune to be working in an environment that supports EJB 3.0. So, for session and conversation scoped JavaBean and entity bean components, Seam provides an extra layer of cluster-safe state management over the top of the web container session clustering.
For session or conversation scoped JavaBean components, Seam automatically forces replication to occur by
calling setAttribute()
once in every request that the component was invoked by the
application. Of course, this strategy is inefficient for read-mostly components. You can control this
behavior by implementing the org.jboss.seam.core.Mutable
interface, or by extending
org.jboss.seam.core.AbstractMutable
, and writing your own dirty-checking logic inside
the component. For example,
@Name("account")
public class Account extends AbstractMutable
{
private BigDecimal balance;
public void setBalance(BigDecimal balance)
{
setDirty(this.balance, balance);
this.balance = balance;
}
public BigDecimal getBalance()
{
return balance;
}
...
}
Or, you can use the @ReadOnly
annotation to achieve a similar effect:
@Name("account")
public class Account
{
private BigDecimal balance;
public void setBalance(BigDecimal balance)
{
this.balance = balance;
}
@ReadOnly
public BigDecimal getBalance()
{
return balance;
}
...
}
For session or conversation scoped entity bean components, Seam automatically forces replication to occur
by calling setAttribute()
once in every request, unless the (conversation-scoped)
entity is currently associated with a Seam-managed persistence context, in which case no replication is
needed. This strategy is not necessarily efficient, so session or conversation scope entity beans
should be used with care. You can always write a stateful session bean or JavaBean component to "manage" the
entity bean instance. For example,
@Stateful
@Name("account")
public class AccountManager extends AbstractMutable
{
private Account account; // an entity bean
@Unwrap
public Account getAccount()
{
return account;
}
...
}
Note that the EntityHome
class in the Seam Application Framework provides a great example
of managing an entity bean instance using a Seam component.
We often need to work with objects that are not Seam components. But we still want to be able to inject
them into our components using @In
and use them in value and method binding expressions,
etc. Sometimes, we even need to tie them into the Seam context lifecycle (@Destroy
, for
example). So the Seam contexts can contain objects which are not Seam components, and Seam provides a couple
of nice features that make it easier to work with non-component objects bound to contexts.
The factory component pattern lets a Seam component act as the instantiator for a
non-component object. A factory method will be called when a context variable is
referenced but has no value bound to it. We define factory methods using the @Factory
annotation. The factory method binds a value to the context variable, and determines the scope of the bound
value. There are two styles of factory method. The first style returns a value, which is bound to the
context by Seam:
@Factory(scope=CONVERSATION)
public List<Customer> getCustomerList() {
return ... ;
}
The second style is a method of type void
which binds the value to the context
variable itself:
@DataModel List<Customer> customerList;
@Factory("customerList")
public void initCustomerList() {
customerList = ... ;
}
In both cases, the factory method is called when we reference the customerList
context
variable and its value is null, and then has no further part to play in the lifecycle of the value. An even
more powerful pattern is the manager component pattern. In this case, we have a Seam
component that is bound to a context variable, that manages the value of the context variable, while
remaining invisible to clients.
A manager component is any component with an @Unwrap
method. This method returns the
value that will be visible to clients, and is called every time a context variable is
referenced.
@Name("customerList")
@Scope(CONVERSATION)
public class CustomerListManager
{
...
@Unwrap
public List<Customer> getCustomerList() {
return ... ;
}
}
The manager component pattern is especially useful if we have an object where you need more control over the
lifecycle of the component. For example, if you have a heavyweight object that needs a cleanup operation when
the context ends you could @Unwrap
the object, and perform cleanup in the
@Destroy
method of the manager component.
@Name("hens")
@Scope(APPLICATION)
public class HenHouse
{
Set<Hen> hens;
@In(required=false) Hen hen;
@Unwrap
public List<Hen> getHens()
{
if (hens == null)
{
// Setup our hens
}
return hens;
}
@Observer({"chickBorn", "chickenBoughtAtMarket"})
public addHen()
{
hens.add(hen);
}
@Observer("chickenSoldAtMarket")
public removeHen()
{
hens.remove(hen);
}
@Observer("foxGetsIn")
public removeAllHens()
{
hens.clear();
}
...
}
Here the managed component observes many events which change the underlying object. The component manages these actions itself, and because the object is unwrapped on every access, a consistent view is provided.