14 Replies Latest reply on Feb 13, 2008 9:15 AM by mygol

    Using rich:datascroller With Seam and Hibernate Pagination

      I prefer to handle all my pagination on the server with Hibernate, but I also want to use the <rich:datascroller> component. My Seam component "knows" the following information:

      1) The total number of page results
      2) The current page number of results
      3) The desired number of results per page
      4) The search results for the current page

      For example, let's say I search for something that returns 43 results, and the user wants 10 results per page. The component knows that there are 5 total pages, that there are 10 results per page, and which results go with which page (page 0 has results 0-9 and page 4 has results 40-42 for example). The component also serves out the results one page at a time.

      All the rich:datascroller needs to know is that it is getting one page of five, but it can retrieve information about the entire result set (like total pages) from the Seam component.

      How do I use rich:datascroller with the information I have in my Seam component to facilitate pagination? Is this even possible without essentially writing my own component?

      Thanks.

        • 1. Re: Using rich:datascroller With Seam and Hibernate Paginati
          brachie
          • 2. Re: Using rich:datascroller With Seam and Hibernate Paginati

            Thanks, Alexander. I actually caught that before. It is true that the approach you directed me to isn't the same as writing my own component, but man is it a lot of code!

            Besides, even if I were to create my own DataModel, I am currently using the @DataModel and @DataModelSelection annotations in the context of a conversation, and I don't know how that process works with my own DataModel. I suppose I can outject my own DataModel like any other object, but how can I get the selection into the conversational context in an easy way?

            It seems to me easier to just add my own navigation buttons and allow the user to page through the results that way. After all, most of the logic for handling that is in my Seam component.

            I would appreciate any insight into some alternative approaches that others may have tried would be much appreciated.

            Thanks.

            • 3. Re: Using rich:datascroller With Seam and Hibernate Paginati
              pmuir

              You should be able to write some integration code to back the datascroller onto your component. See for example the code I wrote to do this in Trinidad (in org.jboss.seam.trinidad in the seamdiscs example)

              • 4. Re: Using rich:datascroller With Seam and Hibernate Paginati

                So I have done some research, and I have found that it actually isn't the rich:datascroller I should be focusing on but rather the rich:dataTable to which it is bound. It appears to me I need to do two things.

                First, I need to write my own DataModel. It seems I am supposed to use the RichFaces class ExtendedDataModel to help manage paging. Here is an implementation of a SerializableDataModel, which subclasses ExtendedDataModel, from the RichFaces site:

                package org.richfaces.demo.extendeddatamodel;
                
                
                
                import java.io.IOException;
                
                import java.util.ArrayList;
                
                import java.util.HashMap;
                
                import java.util.List;
                
                import java.util.Map;
                
                
                
                import javax.faces.context.FacesContext;
                
                
                
                import org.ajax4jsf.model.DataVisitor;
                
                import org.ajax4jsf.model.Range;
                
                import org.ajax4jsf.model.SequenceRange;
                
                import org.ajax4jsf.model.SerializableDataModel;
                
                /**
                
                 *
                
                 * @author ias
                
                 * This is example class that intended to demonstrate use of ExtendedDataModel and SerializableDataModel.
                
                 * This implementation intended to be used as a request scope bean. However, it actually provides serialized
                
                 * state, so on a post-back we do not load data from the data provider. Instead we use data that was used
                
                 * during rendering.
                
                 * This data model must be used together with Data Provider, which is responsible for actual data load
                
                 * from the database using specific filtering and sorting. Normally Data Provider must be in either session, or conversation
                
                 * scope.
                
                 */
                
                public class AuctionDataModel extends SerializableDataModel {
                
                
                
                 private AuctionDataProvider dataProvider;
                
                 private Integer currentPk;
                
                 private Map<Integer,AuctionItem> wrappedData = new HashMap<Integer,AuctionItem>();
                
                 private List<Integer> wrappedKeys = null;
                
                 private boolean detached = false;
                
                
                
                 /**
                
                 *
                
                 */
                
                 private static final long serialVersionUID = -1956179896877538628L;
                
                
                
                 /**
                
                 * This method never called from framework.
                
                 * (non-Javadoc)
                
                 * @see org.ajax4jsf.model.ExtendedDataModel#getRowKey()
                
                 */
                
                 @Override
                
                 public Object getRowKey() {
                
                 return currentPk;
                
                 }
                
                 /**
                
                 * This method normally called by Visitor before request Data Row.
                
                 */
                
                 @Override
                
                 public void setRowKey(Object key) {
                
                 this.currentPk = (Integer) key;
                
                
                
                 }
                
                 /**
                
                 * This is main part of Visitor pattern. Method called by framework many times during request processing.
                
                 */
                
                 @Override
                
                 public void walk(FacesContext context, DataVisitor visitor, Range range, Object argument) throws IOException {
                
                 int firstRow = ((SequenceRange)range).getFirstRow();
                
                 int numberOfRows = ((SequenceRange)range).getRows();
                
                 if (detached) { // Is this serialized model
                
                // Here we just ignore current Rage and use whatever data was saved in serialized model.
                
                // Such approach uses much more getByPk() operations, instead of just one request by range.
                
                // Concrete case may be different from that, so you can just load data from data provider by range.
                
                // We are using wrappedKeys list only to preserve actual order of items.
                
                 for (Integer key:wrappedKeys) {
                
                 setRowKey(key);
                
                 visitor.process(context, key, argument);
                
                 }
                
                 } else { // if not serialized, than we request data from data provider
                
                 wrappedKeys = new ArrayList<Integer>();
                
                 for (AuctionItem item:dataProvider.getItemsByrange(new Integer(firstRow), numberOfRows, null, true)) {
                
                 wrappedKeys.add(item.getPk());
                
                 wrappedData.put(item.getPk(), item);
                
                 visitor.process(context, item.getPk(), argument);
                
                 }
                
                 }
                
                 }
                
                 /**
                
                 * This method must return actual data rows count from the Data Provider. It is used by pagination control
                
                 * to determine total number of data items.
                
                 */
                
                 private Integer rowCount; // better to buffer row count locally
                
                 @Override
                
                 public int getRowCount() {
                
                 if (rowCount==null) {
                
                 rowCount = new Integer(getDataProvider().getRowCount());
                
                 return rowCount.intValue();
                
                 } else {
                
                 return rowCount.intValue();
                
                 }
                
                 }
                
                 /**
                
                 * This is main way to obtain data row. It is intensively used by framework.
                
                 * We strongly recommend use of local cache in that method.
                
                 */
                
                 @Override
                
                 public Object getRowData() {
                
                 if (currentPk==null) {
                
                 return null;
                
                 } else {
                
                 AuctionItem ret = wrappedData.get(currentPk);
                
                 if (ret==null) {
                
                 ret = getDataProvider().getAuctionItemByPk(currentPk);
                
                 wrappedData.put(currentPk, ret);
                
                 return ret;
                
                 } else {
                
                 return ret;
                
                 }
                
                 }
                
                 }
                
                
                
                 /**
                
                 * Unused rudiment from old JSF staff.
                
                 */
                
                 @Override
                
                 public int getRowIndex() {
                
                 throw new UnsupportedOperationException();
                
                 }
                
                
                
                 /**
                
                 * Unused rudiment from old JSF staff.
                
                 */
                
                 @Override
                
                 public Object getWrappedData() {
                
                 throw new UnsupportedOperationException();
                
                 }
                
                
                
                 /**
                
                 * Never called by framework.
                
                 */
                
                 @Override
                
                 public boolean isRowAvailable() {
                
                 if (currentPk==null) {
                
                 return false;
                
                 } else {
                
                 return getDataProvider().hasAuctionItemByPk(currentPk);
                
                 }
                
                 }
                
                
                
                 /**
                
                 * Unused rudiment from old JSF staff.
                
                 */
                
                 @Override
                
                 public void setRowIndex(int rowIndex) {
                
                 throw new UnsupportedOperationException();
                
                 }
                
                
                
                 /**
                
                 * Unused rudiment from old JSF staff.
                
                 */
                
                 @Override
                
                 public void setWrappedData(Object data) {
                
                 throw new UnsupportedOperationException();
                
                 }
                
                
                
                 /**
                
                 * This method suppose to produce SerializableDataModel that will be serialized into View State and used on a post-back.
                
                 * In current implementation we just mark current model as serialized. In more complicated cases we may need to
                
                 * transform data to actually serialized form.
                
                 */
                
                 public SerializableDataModel getSerializableModel(Range range) {
                
                 if (wrappedKeys!=null) {
                
                 detached = true;
                
                // Some activity to detach persistent data from wrappedData map may be taken here.
                
                // In that specific case we are doing nothing.
                
                 return this;
                
                 } else {
                
                 return null;
                
                 }
                
                 }
                
                 /**
                
                 * This is helper method that is called by framework after model update. In must delegate actual database update to
                
                 * Data Provider.
                
                 */
                
                 @Override
                
                 public void update() {
                
                 getDataProvider().update();
                
                 }
                
                
                
                 public AuctionDataProvider getDataProvider() {
                
                 return dataProvider;
                
                 }
                
                
                
                 public void setDataProvider(AuctionDataProvider dataProvider) {
                
                 this.dataProvider = dataProvider;
                
                 }
                
                
                
                }
                


                Second, I need Seam to install my custom DataModel so that it is the one applied to the collection when I use @DataModel in a Seam component. It seems to me the way to do that is similar to what you find here (from Pete's seamdiscs example):

                package org.jboss.seam.trinidad;
                
                import static org.jboss.seam.ScopeType.STATELESS;
                import static org.jboss.seam.annotations.Install.FRAMEWORK;
                
                import javax.faces.model.DataModel;
                
                import org.jboss.seam.annotations.Install;
                import org.jboss.seam.annotations.Name;
                import org.jboss.seam.annotations.Scope;
                import org.jboss.seam.annotations.intercept.BypassInterceptors;
                import org.jboss.seam.faces.DataModels;
                import org.jboss.seam.framework.EntityQuery;
                import org.jboss.seam.framework.HibernateEntityQuery;
                import org.jboss.seam.framework.Query;
                
                /**
                 * Provide enhanced features when Trinidad is used as a JSF component set
                 * @author pmuir
                 *
                 */
                
                @Name("org.jboss.seam.faces.dataModels")
                @Install(precedence=FRAMEWORK, classDependencies="org.apache.myfaces.trinidad.component.UIXComponent")
                @Scope(STATELESS)
                @BypassInterceptors
                public class TrinidadDataModels extends DataModels
                {
                
                 @Override
                 public DataModel getDataModel(Query query)
                 {
                 // If an EntityQuery is in use we can return a CollectionModel
                 // backed by the database
                 if (query instanceof EntityQuery)
                 {
                 return new EntityCollectionModel((EntityQuery) query);
                 }
                 else if (query instanceof HibernateEntityQuery)
                 {
                 return new HibernateEntityCollectionModel((HibernateEntityQuery) query);
                 }
                 else
                 {
                 return super.getDataModel(query);
                 }
                 }
                
                
                }
                
                


                Now I am not using the Seam CRUD stuff. However, I think if I create my ExtendedDataModel implementation and annotate it with @Install(precedence=FRAMEWORK) and give it the right name, then that should work.

                I suppose I am wondering now if the steps I describe are correct and if they are sufficient.

                Thanks.

                • 5. Re: Using rich:datascroller With Seam and Hibernate Paginati
                  brachie

                  @neilac333:

                  I think it is a good idea that someone focuses on a generic database backed pagination for the Richfaces datatable. Unfortunately I had no time to do so and ended up in using Trinidads table (for our project sorting and paging is needed) with in-memory pagination/sorting, because Pete's code gives me an exception when sorting/paging through a very large resultset. Besides it is not really fast and does a lot of count(*) when paging to the next page.

                  I would appreciate if you could keep me/us updated with your results / researches on this topic! :-)

                  Regards,

                  Alexander

                  • 6. Re: Using rich:datascroller With Seam and Hibernate Paginati

                    As I keep studying Pete's seamdiscs code, I feel less confident about how much I can really use beyond concepts. I understand the seamdiscs example is meant to highlight the Seam CRUD application framework, but as a result I think a lot of the data model integration doesn't apply to my case.

                    Let me ask generally...I am aware that @DataModel takes a collection and wraps it in the appropriate JSF DataModel. If I were to write a DataModel that sublasses org.ajax4jsf.ajax.repeat.SerializableDataModel (which incidentally doesn't seem trivial to me anyway), should I simply manually perform the same kind of outjection that @DataModel and @DataModelSelection provide?

                    Basically, I am trying to get the flexible paging ability while staying as close to "Seam doctrine" as possible. Any insight to that end is appreciated.

                    Thanks.

                    • 7. Re: Using rich:datascroller With Seam and Hibernate Paginati
                      pmuir

                      Yeah. If you are going to back your JSF (Extended)DataModel by a database for paging/sorting you need some sort of API to do the paging/sorting (which EntityManager doesn't really provide). So, if you don't want to use EntityQuery, then you will need to some other API that does the paging/sorting.

                      The org.jboss.seam.faces.dataModels component is used to select the correct DataModel to create when Seam creates the datamodel for you (e.g. @DataModel, entityQuery.dataModel).

                      The seamdiscs code is much more proof of concept that a production ready example - hence why it lives with the example, not in seam core ;)

                      If you do succeed with RichFaces, writing about it on SeamFramework.org would be very helpful for others.

                      • 8. Re: Using rich:datascroller With Seam and Hibernate Paginati

                        I've got the paging and sorting covered in a component that abstracts Hibernate Search's ability to handle all that.

                        My first crack at this problem was to use two more Seam components: the first is an "action" component to interact with the user and the second to serve as my ExtendedDataModel instance injected into the first component. The action component takes the parameters and delegates them to that search component I mentioned at the start, which then returns the results to the action component. Then the search component passes those results to the data model component, which wraps them up in a way RichFaces expects to present them to the user.

                        Sounds great in theory, right?

                        For some reason, the data gets populated as I expect, but it "disappears." I suppose there is some kind of weird JSF-RichFaces lifecycle thing that somehow replaces the populated ExtendedDataModel with a new one. I guess I need to try to play with some Seam context scopes to see if I can keep the original one.

                        Just wanted to keep you apprised of my progress.

                        I appreciate any insight.

                        Thanks.

                        • 9. Re: Using rich:datascroller With Seam and Hibernate Paginati

                          I have it working. I even passed in a map instead of an entities using

                          select new Map(....) from MyEntity


                          hibernate syntax. I will post some more code if anyone is interested.

                          See

                          My example
                          http://www.jboss.com/index.html?module=bb&op=viewtopic&t=125952

                          Scroller Reset Solution
                          http://www.jboss.com/index.html?module=bb&op=viewtopic&t=128487
                          http://www.jboss.com/index.html?module=bb&op=viewtopic&t=128606

                          I have improved my original example some some. I will try to help, if you need some.

                          • 10. Re: Using rich:datascroller With Seam and Hibernate Paginati

                            Thanks to everyone for their insight. I have got something that seems to be working. I don't know if it is the most elegant code ever written, but I will be happy to write up the general solution on SeamFramework.org. Just let me know where, Pete.

                            For those who might be interested, let me first point out I have three components working together:

                            1) An action component that interacts with the user
                            2) A persistence component that abstracts all the search APIs
                            3) My own SerializedDataModel which is bijected from my Seam action component.

                            The action component basically takes the search parameters from the user and passes them to the persistence component. That component returns the results to the action, which then passes them to the SerializedDataModel, which wraps the information in a HashMap by key as specified in the examples and Jason Long's code.

                            As for the paging, when the user wants a different page of results (either by clicking one of the navigation buttons or by clicking an actual page number), the action component uses the DataScrollerEvent passed to the listener method within to figure out where the user wants to go next. The action component passes the desired page number to the persistence component, which of course is handling all the result paging. The persistence component then passes back the results for the desired page, and we do all the data passing around I described above.

                            Again, I will be happy to elaborate further on SeamFramework.org since I think this will be a common issue for developers until RichFaces comes up with a better way to handle this.

                            Thanks.

                            • 11. Re: Using rich:datascroller With Seam and Hibernate Paginati
                              pmuir
                              • 12. Re: Using rich:datascroller With Seam and Hibernate Paginati

                                I will try to write something up as soon as I can.

                                In the meantime, I am curious about an ancillary aspect of all this--the testing. My Seam action component has a listener method that handles the passing of the desired page number from the client to the search code, and this method takes a DataScrollerEvent parameter. This is an object unique to RichFaces. How can I test the overall function of my action class? In general, when testing Seam components that have dependencies on FacesEvent classes and so on, what is the best way to test these? Is there an existing mock library? Should I write my own stub for such a class? Should the test be an integration test that generates a real DataScrollerEvent within JBoss embedded? If so, how would I trigger it?

                                Any insight here would be helpful. Especially since I think that should be part of the write-up on the wiki.

                                Thanks.

                                • 13. Re: Using rich:datascroller With Seam and Hibernate Paginati
                                  pmuir

                                   

                                  "neilac333" wrote:
                                  My Seam action component has a listener method that handles the passing of the desired page number from the client to the search code, and this method takes a DataScrollerEvent parameter. This is an object unique to RichFaces. How can I test the overall function of my action class?In general, when testing Seam components that have dependencies on FacesEvent classes and so on, what is the best way to test these? Is there an existing mock library?


                                  I think you are on the right path with mocks. Ask on the RichFaces forum too, I know Alex has some testing stuff in RichFaces. Alternatively, you can look at JSFUnit.

                                  Should I write my own stub for such a class?


                                  If you do, let us know what you do, this could be interesting for Seam.

                                  Should the test be an integration test that generates a real DataScrollerEvent within JBoss embedded?


                                  Seam integration tests don't reach down as far as the view layer, but simulate (mock out) JSF. We don't have any support for mock FacesEvents, but I would definitely accept this as an extension to Seam test. Again, you could look at JSFUnit (it's a new project, but it looks very interesting - I'm hoping that we can start using it in the seam testsuite soon).

                                  HTH

                                  • 14. Re: Using rich:datascroller With Seam and Hibernate Paginati
                                    mygol

                                    can you send me your code?
                                    my email:my_hotjava@hotmail.com

                                    thanks