You can find the full source code for this website in the Seam package in the directory /examples/wiki. It is licensed under the LGPL.
Seam is an application framework that enables the development of truly stateful web applications. In order to do this, the framework provides the following features:
Both the official reference books for Seam, as well as those available from third parties, do not focus on how the achieve the above. While the framework does provide transaction and conversation management, the persistence context, etc, the task of hiding these details from the programmer is ours, and therefore that is what we will do next.
So many promises !!In the following article, we will design the classes needed for the management of a single use case, with a model that will make the above problems invisible to the 'end' programmer.
The following is the source code for a use case that will create a person, his/her relatives, and their phone numbers. We want to design a 3-page wizard: one for the person, another for the relatives, and a third one for their phone numbers. We will use EL expressions in order to make the code shorter.
invokeMethod("#{createPersonUseCase.beginUseCase}"); (1) setValue("#{personHome.instance.firstName}", "Anakin"); setValue("#{personHome.instance.lastName}", "Skywalker"); (2) invokeMethod("#{personHome.persist}"); (3) setValue("#{relativeHome.instance.firstName}", "Padme"); setValue("#{relativeHome.instance.lastName}", "Amidala"); setValue("#{relativeHome.instance.relation}", "wife"); (4) invokeMethod("#{relativeHome.persist}"); (5) invokeMethod("#{relativeHome.instance.clearInstance}"); setValue("#{relativeHome.instance.firstName}", "Luke"); setValue("#{relativeHome.instance.lastName}", "Skywalker"); setValue("#{relativeHome.instance.relation}", "son"); (6) invokeMethod("#{relativeHome.persist}"); (7) setValue("#{phoneHome.instance.number}", "555-1111"); invokeMethod("#{phoneHome.persist}"); (8) invokeMethod("#{phoneHome.clearInstance}"); setValue("#{phoneHome.instance.number}", "555-1234"); invokeMethod("#{phoneHome.persist}"); (9) invokeMethod("#{createPersonUseCase.finishUseCase}"); (10)
Like it can be seen in the previous code snippet, there are no explicit references to transactions, contexts, conversations, persistence, etc. Everything is hidden from the developer. Here is the step-by-step detail of the tasks done by Seam in the example code:
With the architecture already defined, we will now show the source code for the classes used in the previous example. We will start with the class for component createPersonUseCase:
package com.test.seam.usecases; @Name("abstractCreatePersonUseCase") @Scope(ScopeType.CONVERSATION) @Conversational public class CreatePersonUseCase extends UseCase implements Serializable { private static final long serialVersionUID = 8270494158481079247L; }
The functionality to create and end the transaction, the conversation, etc, is encapsulated in the UseCase superclass. Therefore, if the use case does not require special start and end requirements, it is complete.
So far, the actual code the developer needs to write is minimal. Let's continue with the Home objects, starting with personHome:
package com.test.homes; @Name("personHome") @Scope(ScopeType.CONVERSATION) @Conversational public class PersonHome extends EntityHome<Person> implements Serializable { private static final long serialVersionUID = -505760018509003694L; }
Done! The persistence functionality is implemented in the superclass provided by Seam. We specify <Person> in order to indicate the type of domain object wrapped by the Home. Let's proceed with the class for relativeHome:
package com.test.homes; @Name("relativeHome") @Scope(ScopeType.CONVERSATION) @Conversational public class RelativeHome extends EntityHome<Relative> implements Serializable { private static final long serialVersionUID = -505760018509003694L; @In PersonHome personHome; /* (non-Javadoc) * @see org.jboss.seam.framework.Home#createInstance() */ protected Relative createInstance() { Relative relative=new Relative(); relative.setPerson(personHome.getInstance()); return relative; } }
As we can see, the personHome is injected into this EntityHome, and is then used to obtain and assign the person instance during the creation of the domain object relative. No further coding is necessary.
The code for phoneHome is identical to the above example, except changing the word relative for phone.
Since class CreatePersonUseCase is conversational, it requires an existing long conversation in order to be instantiated by Seam's metacontainer. We define a factory tasked with the instantiation and ejection of the conversation, using the UseCaseManager.
package com.test.seam.usecases; @Name("createPersonUseCaseFactory") public class CreatePersonUseCaseFactory implements Serializable { @Factory(value="createPersonUseCase",scope=ScopeType.CONVERSATION) public CreatePersonUseCase getCreatePersonUseCase() { return new UseCaseManager<CreatePersonUseCase>().createUseCase(CreatePersonUseCase.class); } }
As can be seen, the factory ejects component createPersonUseCase into the metacontainer.
That's it. Our hypothetical programmer does not need to write any additional code in order to have a complete use case (with the exception, of course, of coding the domain objects Person, Relative and Phone, which are all very simple).
How did we manage to hide the functionality needed for the use case to work? The following is the code for the UseCaseManager, which should never be seen by our programmer:
package com.test.seam.usecases; public class UseCaseManager<E extends UseCase> implements Serializable { private static final long serialVersionUID = -4291304878869343425L; public E createUseCase(Class<E> useCase) { Conversation.instance().begin(); Conversation.instance().changeFlushMode(FlushModeType.MANUAL); E e=(E)Component.getInstance(useCase,true); return e; } }
Method createUseCase is in charge of creating the conversation, telling the corresponding ORM that the FlushMode will be MANUAL, and instantiating the Seam component ( in our case, CreatePersonUseCase ).
Finally, here is our object UseCase as the icing on the cake:
package com.test.seam.usecases; public abstract class UseCase implements Serializable { private EntityManager entityManager; public String beginUseCase() { entityManager= (EntityManager) Component.getInstance("entityManager"); return Constants.SUCCESS; } public String finishUseCase() { entityManager.flush(); Conversation.instance().end(); return Constants.SUCCESS; } public String cancelUseCase() { entityManager.clear(); Conversation.instance().end(); return Constants.SUCCESS; } }
The only caveat of this class is that we must access the ORM's EntityManager in order to confirm the end of the transaction, either by sending the persistence context to the database with flush() or by cleaning it with clear(), and then marking the end of the conversation so that all the EntityHomes and UseCases are deleted.