We've added some cool features:
- dynamic parameters (if you don't want to expose your EntityQuery via EL)
- EntityQueryDataModel (to make DB pagination for JSF dataTable a breaze)
Installation
All you need to do is attach this jar or add following Maven entry:<dependency> <groupId>pl.com.it-crowd</groupId> <artifactId>seam3-persistence-framework</artifactId> <version>1.0.0</version> </dependency>Of course you need to add our artifactory to your settings.xml:
<repositories> <repository> <id>it-crowd.com.pl</id> <url>http://artifactory.it-crowd.com.pl/repo</url> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories>Now your're ready to extend EntityHome or EntityQuery.
public class UserHome extends EntityHome<User> { }Does it look familiar? It's identical as in Seam2!
Differences
We don't like that Seam2 bound EntityHome and EntityQuery too tightly to view layer. We usually used those components for more then just a CRUD. Actually we were re-using them wherever possible.Scope & EL
So in Seam3 by default EntityHome and EntityQuery are in Dependant scope and don't have to be accessible via EL. If you want your i.e. UserHome to be accessible via EL and reside in Conversation scope you're free to do this:@Named @ConversationScoped public class UserHome extends EntityHome<User> { }
Return values
Previously EntityHome was returningString
values from persist()
, update()
,
remove()
methods.
It's because they were ment to be used in JSF
navigation rules (I think).
Because we now want to decouple our components from view layer those methods will return booleans
. Entity converter
There is no mores:converter
but we've got another one: <h:selectOneListbox id="city" value="#{citySelector.selectedCity}" size="1" onchange="#{rich:element('hM')}.submit()">
<f:selectItem itemValue="#{null}" itemLabel="#{messages['CRUD.all']}"/>
<f:selectItems value="#{citySelector.availableCities}" var="city" itemValue="#{city}"
itemLabel="#{city.getName(localeSelector.selectedLocale.language)}"/>
<f:converter converterId="entityConverter"/>
</h:selectOneListbox>
What is cool with this converter, it can handle both null values and transient objects (careful). But there is one
requirement, your entity must
implement
pl.com.it_crowd.seam.framework.Identifiable
interface:public interface Identifiable<T> { public T getId(); }If your entity's
getId()
method returns null then it is treated as transient entity and during
conversion from string to object
a new instance will be created, so if you've written something into transient entity and convert it using
EnityConverter then that info will be
lost.
So be careful, and don't you even think about synchronizing/waiting on such entity!Re-use and decoupling
We've found it most usefull if EntityHome and EntityQuery are in DependantScope and are not available via EL. This is because sometimes we want to use them as DAO or business logic in different components that are used at the same time but each of those components set's up EntityHome or EntityQuery differently.Let's see an example. We've got a page where we show a table with all events but at the top we also have a box where we show only future events. Both lists can easily be backed by 2 instances of the same EntityQuery. Let's see code abstract.
Below is backing bean for the main list.
@Named @ViewScoped public class AllEventsView { // ------------------------------ FIELDS ------------------------------ @Inject @Archive private EventList eventList; // --------------------- GETTER / SETTER METHODS --------------------- public EventList getEventList() { return eventList; } }Here we've got a backing bean for future events box, a Request scoped bean available via EL as
futureEvents
.@Model public class FutureEvents { // ------------------------------ FIELDS ------------------------------ @Inject @Future private EventList eventList; // --------------------- GETTER / SETTER METHODS --------------------- public EventList getEventList() { return eventList; } }We also need a producer for our lists.
public class EventListProducer { // ------------------------------ FIELDS ------------------------------ @Inject private EventList eventList; // -------------------------- OTHER METHODS -------------------------- @Produces @Archive public EventList getArchive() { return eventList; } @Produces @NextBets public EventList getNextBets() { eventList.getSearchCriteria().setFutureOnly(true); return eventList; } }And finally our EventList.
public class EventList extends EntityQuery <Event> implements Serializable { // ------------------------------ FIELDS ------------------------------ private static final String DATE_SORT_FIELD = "e.date"; private SearchCriteria searchCriteria = new SearchCriteria(); // --------------------------- CONSTRUCTORS --------------------------- public EventList() { setEjbql("select e from Event e"); AbstractCondition nextBetsCondition = new FreeCondition("e.date>=", searchCriteria.futureOnlyBridge); setConditions(Arrays.<AbstractCondition>asList(nextBetsCondition)); toggleSort(DATE_SORT_FIELD); } // --------------------- GETTER / SETTER METHODS --------------------- public SearchCriteria getSearchCriteria() { return searchCriteria; } // -------------------------- INNER CLASSES -------------------------- public class SearchCriteria { // ------------------------------ FIELDS ------------------------------ private boolean futureOnly; private DynamicParameter futureOnlyBridge = new DynamicParameter() { @Override public Object getValue() { return futureOnly ? new Date() : null; } }; public boolean isFutureOnly() { return futureOnly; } public void setFutureOnly(boolean futureOnly) { this.futureOnly = futureOnly; } } }Notice that our EventList doesn't have any scope annotatnios nor is it @Named. We just inject it into view backing beans like FutureEvents and AllEventsView. We could of course put it into Conversation or View scope and make it @Named but then we would have the same instance of component injected into FutureEvents and AllEventsView and we don't want such situation. We could alternatively create a producer that would produce @Named EventList which is fine, but from my experience it's better to introduce the view layer which needs to tweak the EventList up before exposing it to facelets.
EntityQueryDataModel
How do you implement DB pagination? It's out-of-the-box for RichFaces components. Just instead of #{bean.eventList.resultList} bind #{bean.eventList.dataModel} to value attribute of your rich component and set "rows" attribute. EntityQueryDataModel will now load only 20 items from DB per page and rich:dataScroller takes care of switching pages back and forth.<rich:dataTable id="lE" value="#{allEventsView.eventList.dataModel}" var="event" rows="20"> <rich:column> <f:facet name="header">ID</f:facet> #{event.id} </rich:column> <rich:column> <f:facet name="header">name</f:facet> <h:link value="#{event.name}" outcome="/view/event/eventDetails.xhtml"> <f:param name="id" value="#{event.id}"/> </h:link> </rich:column> <f:facet name="footer"> <rich:dataScroller/> </f:facet> </rich:dataTable>
Dynamic parameters without EL
In Seam2 we used to add restrictions which contained EL expressions. It was enough to change value of bean's attribute that was targeted via such EL expression to have the EntityQuery refreshed results for new criteria. We found it that usually we were using the EntityQuery alone as such backing bean. Because the EntityQuery was exposed to EL it was no problem to use it's name in restrictions. But problems arised when we wanted to have two EntityQueries of the same type in single page with different criteria. I know we could solve such issue with @Role but we would then end up with very many roles, probably one for each view.Our solution was to make EntityQuery a dependant bean, not exposed to EL. But this way we couldn't use it in EL restrictions. To resolve this we've created
Conditions
.You've seen them earlier in this article. Let's look at it once again.
public EventList() { setEjbql("select e from Event e"); AbstractCondition nextBetsCondition = new FreeCondition("e.date>=", searchCriteria.futureOnlyBridge); setConditions(Arrays.<AbstractCondition>asList(nextBetsCondition)); toggleSort(DATE_SORT_FIELD); }We've got several conditions:
- FreeCondition - included into query if all arguments are not null
- AndCondition - included into query if any of arguments (sub-conditions) is not null, joins arguments with logical AND
- OrCondition - included into query if any of arguments (sub-conditions) is not null, joins arguments with logical OR
- IsNullCondition - always included into query, used to tell if argument is null
- NotNullCondition - always included into query, used to tell if argument is not null
In our example above (EventList constructor) the searchCriteria.futureOnlyBridge is a dynamic parameter. It's called bridge (by us) because it usually just delegates to another method. In our example it checks value of futureOnly (boolean) attribute and if it is true it returns current date. Otherwise it returns null. So if we set futureOnly to true the nextBetsCondition will be included into query; If on the other hand set futureOnly to false, then futureOnlyBridge will return null and thus one of FreeCondition arguments will be null and it will not be included into query.
As we've said before FreeCondition (actually Free-,And- and OrCondition) accept any number of arguments (IsNull and NotNullCondition also accept many arguments, but will use only first one).
new FreeCondition("(e.date>=", searchCriteria.futureOnlyBridge," or true=",isAdminBridge,")");The condition above will render either
- (e.date>=:qel0 or true=:qel1) - if both bridges return not null values (:qel0 is a query param that will have set value of searchCriteria.futureOnlyBridge; similarily :qel1 stands for isAdminBridge)
- null - if any dynamic parameter is null
Thanks for the great package Bernard, It works like a charm.
ReplyDeleteI had to overcome a small difficulty though. I use the setRestrictionExpressionStrings method on the EntityQuery class. This method uses the "expressions" injected bean, so I can't call it in the constructor of my class that extends EntityQuery. I can't use @PostConstruct either because there is a "validate" method annotated with it in the Query class.
To work around this small issue, wherever I inject the class (UserList in my first case), I set the restrictions in the @PostConstruct of that class.
Thanks again for the great package!
Sylvian, thanks for feedback. It's very important to know that people are using it.
DeleteYou can override validate method. It should be called on post construct.
I know I can override it, but setting values in a method that is named "validate" doesn't feel right! ;)
DeleteThanks again for the good work!
Mobile playing refers to taking part in} video games of likelihood or ability for money 토토사이트 by using a remote system similar to a tablet laptop, smartphone or a cell phone with a wireless web connection. Over 100 cell casinos have been operating as of December 2013, with a lot of the big casino operators in playing now providing a cell platform for his or her participant base. The main game kinds of on-line playing are betting, casinos, lottery, poker, on-line bingo, and others. A lottery is a sort of playing by which numbers are drawn at random for a prize.
ReplyDelete