7 Replies Latest reply on Feb 19, 2010 12:43 AM by Dean Hiller

    Data scroller backed by EntityQuery

    jk;l jkl; Expert

      I'd like to use rich:dataScroller for a dataTable, but it doesn't work when the backing List comes from the resultList of an EntityQuery - why is that?


      How can I solve this problem? I've seen a few forum posts and webpages on the use of ExtendedDataModel, but I really can't tell from the posts what that code does to solve the issue or whether it'll actually work. Could someone help explain what the issue is, and maybe guide me towards what I should do to solve it?


      Thanks.

        • 1. Re: Data scroller backed by EntityQuery
          jk;l jkl; Expert

          It seems that neilac333 from the old forum solved the problem (see the post: Page 1 and Page 2)


          Does anyone know what became of this - that person was going to post to the knowledgebase or on the seamframework.org site, but I haven't been able to find it? This could solve my problem completely - it's exactly what I'm running into.

          • 2. Re: Data scroller backed by EntityQuery
            M. Parker Newbie

            Here is a piece of code that works for me. It is based on several examples that I found on-line.


            1. Eclectic Programmer Example


            2. Richfaces Datascroler, EntityQuery and Pagination


            3. Implementing Richfaces Extended Data Model Classes


            I deleted some of the mechanics that pertain to my specific application. I created an abstract class for most of the implementation since I have a couple of data tables.
            Although there is some code to track the sorting field, I don't really using it within the Hibernate queries that run in the background.


            First you need a class that extends org.ajax4jsf.model.SerializableDataModel.



            public abstract class ViewDataTableModel extends SerializableDataModel {
                      
                 @Logger
                 private Log log;
            
                      /** The total number of data rows in the current view. **/
                 protected Long totalRows = null;
            
                      /** Field to sort the data. **/
                 protected String sortField = "id";
                 
                      /** Sorting direction related to the sorted field. **/
                 protected boolean descending = false;
                 
                      /** The user's position in the dataset . **/
                 protected int rowIndex;
                 
                      /** The first row displayed in the table. **/
                 protected int firstRow = -1;
            
                 protected List<Long> wrappedKeys = null;
                 
                 
                 /**
                  * Default constructor. 
                  */
                 public ViewDataTableModel(){
                      super();
                 }
                 
                 
                 /* (non-Javadoc)
                  * @see org.ajax4jsf.model.ExtendedDataModel#getSerializableModel(org.ajax4jsf.model.Range)
                  */
                 @Override
                 public SerializableDataModel getSerializableModel(Range range) {
            
                      if ( wrappedKeys !=  null ){
                           detached = true;
                           return this;
                      }
                      
                      return null;
                      
                 }     
                 
            
                 /**
                  * 
                  * @return
                  */   
                    protected Object getSortFieldObject() {   
                        FacesContext context = FacesContext.getCurrentInstance();    
                       Object sortFieldObject =  context.getExternalContext().getRequestParameterMap().get("sortField");    
                       return sortFieldObject;    
                    }  
            
                 /* (non-Javadoc)
                  * @see org.ajax4jsf.model.SerializableDataModel#update()
                  */
                    @Override
                 public void update() {
                                
                     log.info("Updating RawDataTableModel");
                     
                     if (getSortFieldObject() != null){      
                        
                        String newSortField = getSortFieldObject().toString();      
                        if (newSortField.equals(sortField)){      
                           descending = !descending;      
                        } 
                 
                        sortField = newSortField;      
                     }  
                      
                      detached = true;
                 }
                 
                     
                 /**
                  * Not supported.
                  */
                 @Override
                 public Object getWrappedData() {
                      throw new UnsupportedOperationException();
                 }
            
            
                 /**
                  * Not supported.
                  */
                 @Override
                 public void setWrappedData(Object arg0) {
                      throw new UnsupportedOperationException();          
                 }
                 
                 
                 /* (non-Javadoc)
                  * @see org.ajax4jsf.model.ExtendedDataModel#getRowKey()
                  */
                 @Override
                 public Object getRowKey() {
                      return currentPK;
                 }
            
                 /* (non-Javadoc)
                  * @see org.ajax4jsf.model.ExtendedDataModel#setRowKey(java.lang.Object)
                  */
                 @Override
                 public void setRowKey(Object object) {
                      currentPK = (Long)object;
                 }
                 
                 /* (non-Javadoc)
                  * @see javax.faces.model.DataModel#getRowIndex()
                  */
                 @Override
                 public int getRowIndex() {
                      log.info("Returning row index. " + rowIndex );
                      return rowIndex;
                 }
                 
                 /* (non-Javadoc)
                  * @see javax.faces.model.DataModel#setRowIndex(int)
                  */
                 @Override
                 public void setRowIndex(int rowIndex) {
                      this.rowIndex = rowIndex;
                 }
                 
                 /* (non-Javadoc)
                  * @see javax.faces.model.DataModel#getRowCount()
                  */
                 @Override
                 public int getRowCount() {
                 
                      if ( totalRows == null ){
                           log.info("Updating row count first....");
                           updateRowCount();               
                      } 
                      
                      log.info("Row count " + totalRows );
                      return totalRows.intValue();
                      
                 }
                 
                 /**
                  * Returns the number of data rows in the current view.
                  */
                 protected abstract void updateRowCount();
                 
            }
            



            Here is the main implementation that interfaces with Hibernate. ConvolveUI is an entity bean.



            @Name("convolveTableModel")
            @Scope(ScopeType.SESSION)
            public class ConvolveTableModel extends ViewDataTableModel {
            
                 private static final long serialVersionUID = -5268176093221597456L;
            
                 @Logger
                 Log log;
                 
                 @In
                 ConvolveDAOLocal convolveDAO;  
                 
                 private final Map<Long, ConvolveUI> wrappedData = new HashMap<Long, ConvolveUI>();
            
                 /**
                  * Default constructor.
                  */
                 public ConvolveTableModel() {
                      super();
                 }
            
                 /* (non-Javadoc)
                  * @see com.itt.bap.browser.datamodel.ViewDataTableModel#updateRowCount()
                  */
                 @Override
                 protected void updateRowCount() {
                      totalRows = convolveDAO.getTotalRows(display.getLeft(), display.getRight(), display.getTop(), display.getBottom(), display.getStartDate(), display.getEndDate() );
                      log.info( totalRows + " convovlved areas retrieved.");
                 }
            
                 /* (non-Javadoc)
                  * @see org.ajax4jsf.model.ExtendedDataModel#walk(javax.faces.context.FacesContext, org.ajax4jsf.model.DataVisitor, org.ajax4jsf.model.Range, java.lang.Object)
                  */
                 @Override
                 public void walk(FacesContext context, DataVisitor visitor, Range range, Object argument) throws IOException {
                      
                      log.info("walking data model.");
                      
                      boolean viewChanged = this.hasViewChanged();
                      
                      int first = ((SequenceRange)range).getFirstRow();
                      int totalRows = ((SequenceRange)range).getRows();
                                
                      if ( detached && !viewChanged && (firstRow == first) ){
                           for (Long key: wrappedKeys ){
                                setRowKey( key );
                                visitor.process(context, key, argument);
                           }
                      } else {
                           
                           log.info("Requerying data from database. First record: " + first + " Total rows: " + totalRows );
                                          
                           wrappedKeys = new ArrayList<Long>();
                           wrappedData.clear();
                                          
                         
                                       //Retrieve data using Hibernate. Your implmementation here.
                           List<Convolve> temp = convolveDAO.findData();
                                
                           Iterator<Convolve> rows = temp.iterator();
                           
                           while ( rows.hasNext() ){
                                Convolve row = rows.next();
                                row.getUsers();
                                ConvolveUI convolveUI = new ConvolveUI( row, user );
                                wrappedKeys.add( new Long( row.getId() ) );
                                wrappedData.put( new Long( row.getId() ), convolveUI );
                                visitor.process(context, new Long( row.getId() ), argument );
                           }
                                          
                           if ( viewChanged ){     
                                updateRowCount();
                           }
                           
                           firstRow = first;
                           
                      }
                           
            
                 }
            
                 /* (non-Javadoc)
                  * @see javax.faces.model.DataModel#getRowData()
                  */
                 @Override
                 public Object getRowData() {
            
                      if ( currentPK == null ) return null;
                      
                      log.info("Getting convolve area " + currentPK.toString() );
                      return (ConvolveUI)wrappedData.get( currentPK );
                 
                 }
            
            
                 /* (non-Javadoc)
                  * @see javax.faces.model.DataModel#isRowAvailable()
                  */
                 @Override
                 public boolean isRowAvailable() {
                      
                      boolean available = false;
                      
                      if ( currentPK == null ){
                           return false;
                      } else if ( wrappedKeys.contains( currentPK ) ){ 
                           return true;
                      } else if ( wrappedData.entrySet().contains( currentPK ) ){ 
                           return true;          
                      } 
                      
                      log.info("Convolve area available: " + available );
                      
                      return available;
                      
                 }
            
                 public ConvolveUI getWrappedDataItem( Long key ){
                      
                      if ( wrappedData.containsKey( key ) ){
                           return wrappedData.get( key );
                      }
                      
                      return null;
                      
                 }
                 
            
                 /**
                  * Delete the bean.
                  */
                 @Destroy @Remove
                 public void destroy(){
                      System.out.println("Destorying ConvolveTableModel");          
                      wrappedData.clear();
                      wrappedKeys.clear();
                 }     
                 
            }
            



            <rich:tab name="locationConvolvedTab" label="Convolved" switchType="ajax" actionListener="#{locationConvolveTab.select}" immediate="false">
               <a4j:form>
                  <rich:dataTable id="convolveDataTable" value="#{convolveTableModel}" var="convolved" rows="10" sortMode="single">
                       <rich:column>
                                  <!--
                                     Your column definition here
                                  -->
                          </rich:column>    
                       <f:facet name="footer">                                                    
                               <rich:datascroller for="convolveDataTable" maxPages="10" pageIndexVar="pageIndex" pagesVar="pages"/>                                                                          
                       </f:facet>                                                
                     </rich:dataTable>     
               </a4j:form>                                             
            </rich:tab>        
            



            Hopefully this helps...

            • 3. Re: Data scroller backed by EntityQuery
              jk;l jkl; Expert

              Thanks for the code. I tried using it, and I have a couple of questions - where are pageIndexVar="pageIndex" and pagesVar="pages" used? (i.e. where and how do pageIndex and pages get set, so that the dataScroller to make use of these values?)


              When you click the forward or back button of the dataScroller, how do these values get sent to the backing data model for use?


              Also, getRows() and getFirstRow() of Range always seem to return 0. Where and how are these properties set? The issue I'm having is that the previous/next buttons aren't selectable, and no page numbers are appearing on the dataScroller - I imagine it's a Range object issue?


              Hopefully you could please clarify how to properly attempt to use your code samples. Thanks so much.

              • 4. Re: Data scroller backed by EntityQuery
                Andy Gibson Novice

                I saw this post the other day and came back to provide a link to a blog post I just finished describing how to write reusable data table pagination and ordering backed by EntityQuery objects without having to re-write everything or use anything other than standard JSF controls (no trinidad client side sorting).  I also tried to stick to the standard Seam EntityQuery to keep things compatible if you need to retro-fit it.  It solves the problems of SQL injection on the order by clause and handles multiple queries on a page without having to define parameters for each query and includes ajax based ordering and pagination.


                Codeless ajax ordered paginated tables post


                Cheers,


                Andy Gibson

                • 5. Re: Data scroller backed by EntityQuery
                  Nikolai Gagov Newbie
                  This example is working for me. It is based on Dynamic Entity Queries that could be defined also in components.xml:

                  import java.io.IOException;
                  import java.io.Serializable;
                  import java.util.HashMap;
                  import java.util.LinkedList;
                  import java.util.List;
                  import java.util.Map;
                  import java.util.Set;

                  import javax.el.Expression;
                  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;
                  import org.jboss.seam.framework.EntityQuery;
                  import org.richfaces.model.ExtendedFilterField;
                  import org.richfaces.model.FilterField;
                  import org.richfaces.model.Modifiable;
                  import org.richfaces.model.Ordering;
                  import org.richfaces.model.SortField2;

                  import com.bprocess.bmv.tools.synchro.ReflectionUtils;

                  /**
                  * Generic Pagination Data Model that fetches from database only the necessary data displayed per page
                  *
                  * @author Nikolai Gagov (08.12.2008)
                  *
                  * <br><b>History:</b> <br>
                  * 08.12.2008 Nikolai Gagov created <br>
                  * @param <T>
                  */
                  public abstract class PaginatingDataModel<T> extends SerializableDataModel implements Modifiable {

                       private int rowCount = 0;
                       private boolean isDetached = false;
                       private String sortFieldName;
                       private Ordering ordering;
                       private Serializable currentPk;
                       private final List<Serializable> wrappedKeys = new LinkedList<Serializable>();
                       private final Map<Serializable, T> wrappedData = new HashMap<Serializable, T>();
                       private final Map<String, String> filterMap = new HashMap<String, String>();
                       
                       public abstract EntityQuery<T> getEntityQuery();
                       public abstract T getObjectById(Serializable id);
                       public abstract List<String> getRestrictions();
                       
                       
                       @Override
                       public void walk(FacesContext context, DataVisitor visitor, Range range, Object argument) throws IOException {
                            if (isDetached) {
                                 for (Serializable key : wrappedKeys) {
                                      setRowKey(key);
                                      visitor.process(context, key, argument);
                                 }
                            } else {
                                 final int firstRow = ((SequenceRange) range).getFirstRow();
                                 final int numberOfRows = ((SequenceRange) range).getRows();
                                 wrappedKeys.clear();
                                 wrappedData.clear();
                                 final List<T> objects = loadData(firstRow, numberOfRows, sortFieldName, ordering);
                                 for (T object : objects) {
                                      final Serializable id = getId(object);
                                      wrappedKeys.add(id);
                                      wrappedData.put(id, object);
                                      visitor.process(context, id, argument);
                                 }
                            }
                       }

                       @Override
                       public SerializableDataModel getSerializableModel(Range range) {
                            isDetached = true;
                            rowCount = 0;
                            return this;
                       }

                       @Override
                       public void update() {
                            isDetached = false;
                            filterMap.clear();
                            sortFieldName = null;
                       }
                       
                       @Override
                       public int getRowCount() {
                            if (rowCount == 0) {
                                 rowCount = countRecords();
                            }
                            return rowCount;
                       }

                       @Override
                       public Object getRowData() {
                            if (currentPk == null) {
                                 return null;
                            } else {
                                 T object = wrappedData.get(currentPk);
                                 if (object == null) {
                                      object = getObjectById(currentPk);
                                      wrappedData.put(currentPk, object);
                                 }
                                 return object;
                            }
                       }

                       @Override
                       public boolean isRowAvailable() {
                            if (currentPk == null) {
                                 return false;
                            }
                            if (wrappedKeys.contains(currentPk)) {
                                 return true;
                            }
                            if (wrappedData.entrySet().contains(currentPk)) {
                                 return true;
                            }
                            if (getObjectById(currentPk) != null) {
                                 return true;
                            }
                            return false;
                       }
                       
                       @Override
                       public void modify(List<FilterField> filterFields, List<SortField2> sortFields) {
                            isDetached = false;
                            filterMap.clear();
                            if (!filterFields.isEmpty()) {
                                 for (FilterField filterField : filterFields) {
                                      final ExtendedFilterField extendedFilterField = (ExtendedFilterField) filterField;
                                      final String filterValue = extendedFilterField.getFilterValue();
                                      if (filterValue != null && filterValue.length() > 0) {
                                           final String expressionString = extendedFilterField.getExpression().getExpressionString();
                                           final String filterFieldName = getFilterExpressionName(expressionString);
                                           filterMap.put(filterFieldName, filterValue);
                                      }
                                 }
                            }
                            if (!sortFields.isEmpty()) {
                                 final SortField2 sortFild = sortFields.get(0);
                                 final Expression expression = sortFild.getExpression();
                                 final String expressionString = expression.getExpressionString();
                                 ordering = sortFild.getOrdering();
                                 sortFieldName = getExpressionName(expressionString);
                            }
                       }
                       
                       @Override
                       public Object getRowKey() {
                            return currentPk;
                       }

                       @Override
                       public void setRowKey(final Object key) {
                            this.currentPk = (Serializable) key;
                       }

                       @Override
                       public void setRowIndex(int index) {}

                       @Override
                       public int getRowIndex() {
                            throw new UnsupportedOperationException();
                       }

                       @Override
                       public Object getWrappedData() {
                            throw new UnsupportedOperationException();
                       }

                       @Override
                       public void setWrappedData(Object data) {
                            throw new UnsupportedOperationException();
                       }

                       
                       private String getExpressionName(String expressionString) {
                            return expressionString.substring(expressionString.indexOf('.') + 1, expressionString.indexOf('}'));
                       }
                       
                       private String getFilterExpressionName(String expressionString) {
                            return expressionString.substring(expressionString.indexOf('{') + 1, expressionString.indexOf('}'));
                       }

                       private void addFilters(EntityQuery<T> query) {
                            query.setRestrictions(new LinkedList<String>(getRestrictions()));
                            final Set<String> filterFields = filterMap.keySet();
                            for (String filterFieldName : filterFields) {
                                 final String filterValue = filterMap.get(filterFieldName);
                                 final String restrictionToRemove = filterFieldName + " LIKE ";// TODO test recursive removal without LIKE clause
                                 if (filterFieldName.indexOf(".") > 0) {
                                      filterFieldName = filterFieldName.substring(filterFieldName.lastIndexOf(".") + 1);
                                 }
                                 final String newRestriction = restrictionToRemove + "#{" + filterFieldName + "}||''";
                                 org.jboss.seam.contexts.Contexts.getEventContext().set(filterFieldName, filterValue);
                                 removeRestriction(restrictionToRemove);
                                 final List<String> oldRestrictions = query.getRestrictions();
                                 oldRestrictions.add(newRestriction);
                                 query.setRestrictions(oldRestrictions);
                            }
                       }

                       private void addSort(EntityQuery<T> query, String sortField, Ordering ordering) {
                            if (sortField != null) {
                                 if (ordering.equals(Ordering.DESCENDING)) {
                                      query.setOrder(sortField + " desc ");
                                 } else {
                                      query.setOrder(sortField + " asc ");
                                 }
                            }
                       }

                       private List<T> loadData(int firstRow, int numberOfRows, String sortField, Ordering ordering) {
                            final EntityQuery<T> query = getEntityQuery();
                            addFilters(query);
                            addSort(query, sortField, ordering);
                            query.setFirstResult(firstRow);
                            query.setMaxResults(numberOfRows);
                            final List<T> objects = query.getResultList();
                            return objects;
                       }

                       private Serializable getId(T object) {
                            final Serializable id = ReflectionUtils.getPrimaryKey(object);
                            return id;
                       }

                       private int countRecords() {
                            final EntityQuery<T> query = getEntityQuery();
                            addFilters(query);
                            final int containerCount = query.getResultCount().intValue();
                            return containerCount;
                       }
                       
                       private void removeRestriction(String restrictionToRemove) {
                            final EntityQuery<T> query = getEntityQuery();
                            final List<String> restrictions = query.getRestrictions();
                            String foundRestriction = null;
                            for (String restriction : restrictions) {
                                 if (restriction.startsWith(restrictionToRemove)) {
                                      foundRestriction = restriction;
                                      break;
                                 }
                            }
                            if (foundRestriction != null) {
                                 restrictions.remove(foundRestriction);
                                 query.setRestrictions(restrictions);
                            }
                       }

                  }


                  import java.io.Serializable;
                  import java.util.LinkedList;
                  import java.util.List;

                  import javax.persistence.EntityManager;

                  import org.jboss.seam.ScopeType;
                  import org.jboss.seam.annotations.Create;
                  import org.jboss.seam.annotations.In;
                  import org.jboss.seam.annotations.Name;
                  import org.jboss.seam.annotations.Scope;
                  import org.jboss.seam.framework.EntityQuery;

                  import com.bprocess.bmv.domain.bm4.BpsUser;

                  /**
                  * Custom pagination data model for Users screen
                  *
                  * @author Nikolai Gagov (11.12.2008)
                  *
                  * <br><b>History:</b> <br>
                  * 11.12.2008 Nikolai Gagov created <br>
                  */
                  @Name("usersPaginatingDataModel")
                  @Scope(ScopeType.CONVERSATION)
                  public class UsersPaginatingDataModel extends PaginatingDataModel<BpsUser> {

                       private static final long serialVersionUID = 1L;

                       @In
                       private EntityManager entityManager;
                       
                       @In(value="#{allPagePartyUsers}")
                       private EntityQuery<BpsUser> allPagePartyUsers;
                       
                       private List<String> restrictions = new LinkedList<String>();
                       
                       @Create
                       public void startup() {
                            restrictions.addAll(allPagePartyUsers.getRestrictions());
                       }
                       
                       @Override
                       public EntityQuery<BpsUser> getEntityQuery() {
                            return allPagePartyUsers;
                       }

                       @Override
                       public BpsUser getObjectById(Serializable id) {
                            return entityManager.find(BpsUser.class, id);
                       }

                       @Override
                       public List<String> getRestrictions() {
                            return restrictions;
                       }

                  }


                  components.xml:
                  <framework:entity-query name="partnersQuery"
                            ejbql="SELECT DISTINCT comp
                            FROM BpsUvrt uvrt
                            JOIN uvrt.id.bpsComp comp
                            JOIN uvrt.id.bpsUser user
                            WHERE user = #{user}"
                            order="comp.compName" />

                  users.xhtml:
                  <rich:dataTable id="objectList" value="#{usersPaginatingDataModel}" var="user"
                                           rows="#{paginaterPageSize}" reRender="paginationBar">
                  .....
                  <s:div id="paginationBar">
                                 <a4j:form styleClass="paginationBarClass">
                                      <rich:datascroller styleClass="paginationBarLinkClass" for="objectList"
                                           maxPages="5" boundaryControls="auto" fastControls="auto" />
                                 </a4j:form>
                            </s:div>
                  • 6. Re: Data scroller backed by EntityQuery
                    Dean Hiller Expert

                    What scope is everyone using...EVENT I assume so it clears the row count out in case it changes(I mean after the count sql, you run your real sql and you don't want the reall sql trying to page somewhere with no data).


                    ie. if you are in conversation or session, and have 50 rows(50 count), but then user clicks to last page and there is only 40 rows now, the user would run over and count is still 50.


                    I changed to event but am having trouble...my sequenceRange.getFirstRow started returning 0 every time...it used to return the right values....it still does however get the number of pages to display correctly for some odd reason.  I cahnge to conversation and still getFristRow is 0 though so I am not sure what is going on here...any ideas?


                    thanks,
                    Dean

                    • 7. Re: Data scroller backed by EntityQuery
                      Dean Hiller Expert

                      well, that didn't work, needed the datamodel to be in the conversation instead otherwise, every time user chose a row, it reran the query so Im moved datamodel to conversation and outjected the cachedCount as EVENT scope so it would not be cached for entire conversation.  Unfortunately, the dang model is calling getRowCount every time for some damn reason when user makes a selection which I don't want either.


                      hmmmmm, this is a pain to get right.  maybe I should just stick with the bug everyone else has where the cachednumber of items is false over time as the user keeps stepping through the data....but I don't like that too much.