001    // Copyright 2008 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.tynamo.jpa.internal;
016    
017    import java.util.List;
018    
019    import javax.persistence.EntityManager;
020    import javax.persistence.TypedQuery;
021    import javax.persistence.criteria.CriteriaBuilder;
022    import javax.persistence.criteria.CriteriaQuery;
023    import javax.persistence.criteria.Expression;
024    import javax.persistence.criteria.Root;
025    
026    import org.apache.tapestry5.grid.GridDataSource;
027    import org.apache.tapestry5.grid.SortConstraint;
028    import org.apache.tapestry5.ioc.internal.util.Defense;
029    
030    /**
031     * A simple implementation of {@link org.apache.tapestry5.grid.GridDataSource} based on a Hibernate
032     * Session and a known entity class. This implementation does support multiple
033     * {@link org.apache.tapestry5.grid.SortConstraint sort constraints}; however it assumes a direct
034     * mapping from sort constraint property to Hibernate property.
035     * <p/>
036     * This class is <em>not</em> thread-safe; it maintains internal state.
037     * <p/>
038     * Typically, an instance of this object is created fresh as needed (that is, it is not stored
039     * between requests).
040     *
041     * @param <E>
042     */
043    public class JPAGridDataSource<E> implements GridDataSource
044    {
045            protected final EntityManager entityManager;
046    
047            private final Class<E> entityType;
048    
049            private int startIndex;
050    
051            private List preparedResults;
052    
053            public JPAGridDataSource(EntityManager em, Class<E> entityType)
054            {
055                    Defense.notNull(em, "entityManager");
056                    Defense.notNull(entityType, "entityType");
057    
058                    this.entityManager = em;
059                    this.entityType = entityType;
060            }
061    
062            /**
063             * Returns the total number of rows for the configured entity type.
064             */
065            public int getAvailableRows()
066            {
067                    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
068                    CriteriaQuery<Long> resultQuery = cb.createQuery(Long.class);
069                    Root<E> all = resultQuery.from(entityType);
070                    Expression<Boolean> query;
071    
072                    query = additionalConstraints(cb, all);
073    
074                    if (query != null)
075                    {
076                            resultQuery.where(query).select(cb.count(all));
077                    }
078                    else
079                    {
080                            resultQuery.select(cb.count(all));
081                    }
082    
083                    TypedQuery<Long> finalQ = entityManager.createQuery(resultQuery);
084                    Long result = finalQ.getSingleResult();
085    
086                    return result.intValue();
087            }
088    
089            /**
090             * Prepares the results, performing a query (applying the sort results, and the provided start
091             * and end index). The results can later be obtained from {@link #getRowValue(int)} .
092             *
093             * @param startIndex
094             *            index, from zero, of the first item to be retrieved
095             * @param endIndex
096             *            index, from zero, of the last item to be retrieved
097             * @param sortConstraints
098             *            zero or more constraints used to set the order of the returned values
099             */
100            public void prepare(int startIndex, int endIndex, List<SortConstraint> sortConstraints)
101            {
102                    Defense.notNull(sortConstraints, "sortConstraints");
103    
104                    // We just assume that the property names in the SortContraint match the Hibernate
105                    // properties.
106    
107                    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
108                    CriteriaQuery<E> resultQuery = cb.createQuery(entityType);
109                    Root<E> all = resultQuery.from(entityType);
110                    Expression<Boolean> query;
111    
112                    query = additionalConstraints(cb,all);
113    
114                    if (query != null)
115                    {
116                            resultQuery.where(query).select(all);
117                    }
118                    else
119                    {
120                            resultQuery.select(all);
121                    }
122    
123                    for (SortConstraint constraint : sortConstraints)
124                    {
125    
126                            String propertyName = constraint.getPropertyModel().getPropertyName();
127    
128                            switch (constraint.getColumnSort())
129                            {
130    
131                                    case ASCENDING:
132    
133                                            resultQuery.orderBy(cb.asc(all.get(propertyName)));
134                                            break;
135    
136                                    case DESCENDING:
137                                            resultQuery.orderBy(cb.desc(all.get(propertyName)));
138                                            break;
139    
140                                    default:
141                            }
142                    }
143    
144                    TypedQuery<E> finalQ = entityManager.createQuery(resultQuery);
145    
146                    finalQ.setFirstResult(startIndex);
147                    finalQ.setMaxResults(endIndex - startIndex + 1);
148    
149                    this.startIndex = startIndex;
150    
151                    preparedResults = finalQ.getResultList();
152            }
153    
154            /**
155             * Invoked after the main criteria has been set up (firstResult, maxResults and any sort
156             * contraints). This gives subclasses a chance to apply additional constraints before the list
157             * of results is obtained from the criteria. This implementation does nothing and may be
158             * overridden.
159             */
160            protected Expression<Boolean> additionalConstraints(CriteriaBuilder cb, Root<E> root)
161            {
162                    return null;
163            }
164    
165            /**
166             * Returns a row value at the given index (which must be within the range defined by the call to
167             * {@link #prepare(int, int, java.util.List)} ).
168             *
169             * @param index
170             *            of object
171             * @return object at that index
172             */
173            public Object getRowValue(int index)
174            {
175                    return preparedResults.get(index - startIndex);
176            }
177    
178            /**
179             * Returns the entity type, as provided via the constructor.
180             */
181            public Class getRowType()
182            {
183                    return entityType;
184            }
185    }