001    // Copyright 2007-2008 Thiago H. de Paula Figueiredo
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 br.com.arsmachina.dao.hibernate;
016    
017    import java.io.Serializable;
018    import java.util.List;
019    
020    import org.hibernate.Criteria;
021    import org.hibernate.LockMode;
022    import org.hibernate.SessionFactory;
023    import org.hibernate.classic.Session;
024    import org.hibernate.criterion.Example;
025    import org.hibernate.criterion.MatchMode;
026    import org.hibernate.criterion.Order;
027    import org.hibernate.criterion.Projections;
028    import org.hibernate.criterion.Restrictions;
029    
030    import br.com.arsmachina.dao.ReadableDAO;
031    import br.com.arsmachina.dao.SortCriterion;
032    
033    /**
034     * {@link ReadableDAO} implementation using Hibernate. All methods use {@link #getSession()} to get
035     * {@link Session}.
036     * 
037     * @author Thiago H. de Paula Figueiredo
038     * @param <T> the entity class related to this DAO.
039     * @param <K> the type of the field that represents the entity class' primary key.
040     */
041    public abstract class ReadableDAOImpl<T, K extends Serializable> extends BaseHibernateDAO<T, K>
042                    implements ReadableDAO<T, K> {
043    
044            /**
045             * A {@link SortCriterion} array with no elements.
046             */
047            final public static SortCriterion[] EMPTY_SORTING_CRITERIA = new SortCriterion[0];
048    
049            final private String defaultHqlOrderBy = toHqlOrderBy(getDefaultSortCriteria());
050    
051            /**
052             * Returns a HQL <code>order by</code> clause given some {@link SortCriterion}s.
053             * 
054             * @param sortCriteria {@link SortCriterion} instances.
055             * @return a {@link String}.
056             */
057            final public static String toHqlOrderBy(SortCriterion... sortCriteria) {
058    
059                    String string = "";
060    
061                    if (sortCriteria.length > 0) {
062    
063                            StringBuilder builder = new StringBuilder(" ORDER BY ");
064    
065                            for (int i = 0; i < sortCriteria.length - 1; i++) {
066                                    builder.append(sortCriteria.toString());
067                                    builder.append(", ");
068                            }
069    
070                            builder.append(sortCriteria[sortCriteria.length - 1]);
071    
072                            string = builder.toString();
073    
074                    }
075    
076                    return string;
077    
078            }
079    
080            /**
081             * Constructor that takes a {@link Class} and a {@link SessionFactory}.
082             * 
083             * @param clasz a {@link Class}.
084             * @param sessionFactory a {@link SessionFactory}. It cannot be null.
085             */
086            @SuppressWarnings("unchecked")
087            public ReadableDAOImpl(SessionFactory sessionFactory) {
088                    super(null, sessionFactory);
089            }
090    
091            /**
092             * Constructor that takes a {@link Class} and a {@link SessionFactory}.
093             * 
094             * @param clasz a {@link Class}.
095             * @param sessionFactory a {@link SessionFactory}. It cannot be null.
096             */
097            @SuppressWarnings("unchecked")
098            public ReadableDAOImpl(Class<T> clasz, SessionFactory sessionFactory) {
099                    super(clasz, sessionFactory);
100            }
101    
102            /**
103             * @see br.com.arsmachina.dao.ReadableDAO#countAll()
104             */
105            public int countAll() {
106    
107                    final Criteria criteria = createCriteria();
108    
109                    criteria.setProjection(Projections.rowCount());
110    
111                    return (Integer) criteria.uniqueResult();
112    
113            }
114    
115            /**
116             * Returns all the entity class' objects. They are sorted according to
117             * {@link #getDefaultSortCriterions()}.
118             * 
119             * @see br.com.arsmachina.dao.ReadableDAO#findAll()
120             * @see #getDefaultSortCriterions()
121             */
122            @SuppressWarnings("unchecked")
123            public List<T> findAll() {
124    
125                    Criteria criteria = createCriteria();
126                    addSortCriteria(criteria, getDefaultSortCriteria());
127                    return criteria.list();
128    
129            }
130    
131            /**
132             * @see br.com.arsmachina.dao.ReadableDAO#findById(java.io.Serializable)
133             */
134            @SuppressWarnings("unchecked")
135            public T findById(K id) {
136                    return (T) getSession().get(getEntityClass(), id);
137            }
138    
139            /**
140             * @see br.com.arsmachina.dao.ReadableDAO#findByIds(K[])
141             */
142            @SuppressWarnings("unchecked")
143            public List<T> findByIds(K... ids) {
144    
145                    Criteria criteria = createCriteria();
146                    criteria.add(Restrictions.in(getPrimaryKeyPropertyName(), ids));
147                    return criteria.list();
148    
149            }
150    
151            /**
152             * @see br.com.arsmachina.dao.ReadableDAO#findByExample(java.lang.Object)
153             */
154            @SuppressWarnings("unchecked")
155            public List<T> findByExample(T example) {
156    
157                    Criteria criteria = createCriteria();
158    
159                    if (example != null) {
160                            criteria.add(createExample(example));
161                    }
162    
163                    return criteria.list();
164    
165            }
166    
167            /**
168             * @see br.com.arsmachina.dao.WriteableDAO#refresh(java.lang.Object)
169             */
170            public void refresh(T object) {
171                    getSession().refresh(object);
172            }
173    
174            /**
175             * If <code>sortingConstraints</code> is <code>null</code> or empty, this implementation
176             * sort the results by the {@link SortCriterion}s returned by
177             * {@link #getDefaultSortCriterions()}.
178             * 
179             * @see br.com.arsmachina.dao.ReadableDAO#findAll(int, int,
180             * br.com.arsmachina.dao.SortCriterion[])
181             */
182            @SuppressWarnings("unchecked")
183            public List<T> findAll(int firstResult, int maximumResults, SortCriterion... sortingConstraints) {
184    
185                    Criteria criteria = createCriteria();
186                    criteria.setFirstResult(firstResult);
187                    criteria.setMaxResults(maximumResults);
188    
189                    if (sortingConstraints == null || sortingConstraints.length == 0) {
190                            sortingConstraints = getDefaultSortCriteria();
191                    }
192    
193                    addSortCriteria(criteria, sortingConstraints);
194    
195                    return criteria.list();
196    
197            }
198    
199            /**
200             * Reattaches the object to the current {@link org.hibernate.Session} using
201             * <code>Session.lock(object, LockMode.NONE)</code> and then returns the object.
202             * 
203             * @param a <code>T</code>.
204             * @return <code>object</code>.
205             * @see br.com.arsmachina.dao.ReadableDAO#reattach(java.lang.Object)
206             */
207            public T reattach(T object) {
208                    
209                    getSession().lock(object, LockMode.NONE);
210                    return object;
211                    
212            }
213            
214            /**
215             * Adds <code>sortCriteria</code> to a {@link Criteria} instance.
216             * 
217             * @param criteria a {@link Criteria}. It cannot be null.
218             * @param sortCriteria a {@link SortCriterion}<code>...</code>. It cannot be null.
219             * @todo Support for property paths, not just property names.
220             */
221            final public void addSortCriteria(Criteria criteria, SortCriterion... sortCriteria) {
222    
223                    assert criteria != null;
224                    
225                    if (sortCriteria == null || sortCriteria.length == 0) {
226                            sortCriteria = getDefaultSortCriteria();
227                    }
228                    
229                    for (SortCriterion sortingConstraint : sortCriteria) {
230    
231                            final String property = sortingConstraint.getProperty();
232                            final boolean ascending = sortingConstraint.isAscending();
233                            final Order order = ascending ? Order.asc(property) : Order.desc(property);
234                            criteria.addOrder(order);
235    
236                    }
237    
238            }
239    
240            /**
241             * Adds the default sort criteria to a {@link Criteria} instance. This method just does
242             * <code>addSortCriteria(criteria, getDefaultSortCriteria());</code>.
243             * 
244             * @param criteria a {@link Criteria}. It cannot be null.
245             */
246            protected void addSortCriteria(Criteria criteria) {
247                    addSortCriteria(criteria, getDefaultSortCriteria());
248            }
249    
250            /**
251             * Returns the default {@link SortCriterion}s to be used to sort the objects lists returned by
252             * methods like {@link #findAll()} and {@link #findAll(int, int, SortCriterion...)} when no
253             * sorting constraints are given. This implementation returns {@link #EMPTY_SORTING_CRITERIA}.
254             * 
255             * @return a {@link SortCriterion} array. It cannot be <code>null</code>.
256             */
257            public SortCriterion[] getDefaultSortCriteria() {
258                    return EMPTY_SORTING_CRITERIA;
259            }
260    
261            /**
262             * Creates a {@link Criteria} for this entity class.
263             * 
264             * @return a {@link Criteria}.
265             */
266            public Criteria createCriteria() {
267                    return getSession().createCriteria(getEntityClass());
268            }
269    
270            /**
271             * Creates a {@link Criteria} for this entity class with given sort criteria.
272             * 
273             * @return a {@link Criteria}.
274             */
275            public Criteria createCriteria(SortCriterion ... sortCriteria) {
276                    
277                    Criteria criteria = createCriteria();
278                    addSortCriteria(criteria, sortCriteria);
279                    return criteria;
280                    
281            }
282    
283            /**
284             * Creates a {@link Criteria} for this entity class with given sort criteria,
285             * first result index and maximum number of results. 
286             * 
287             * @return a {@link Criteria}.
288             */
289            public Criteria createCriteria(int firstIndex, int maximumResults, SortCriterion ... sortCriteria) {
290                    
291                    Criteria criteria = createCriteria(sortCriteria);
292                    criteria.setFirstResult(firstIndex);
293                    criteria.setMaxResults(maximumResults);
294                    return criteria;
295                    
296            }
297    
298            /**
299             * Used by {@link #findByExample(Object)} to create an {@link Example} instance.
300             * 
301             * @todo add criteria for property types not handled by Example (primary keys, associations,
302             * etc)
303             * @return an {@link Example}.
304             */
305            public Example createExample(T entity) {
306    
307                    Example example = Example.create(entity);
308                    example.enableLike(MatchMode.ANYWHERE);
309                    example.excludeZeroes();
310                    example.ignoreCase();
311    
312                    return example;
313    
314            }
315    
316            /**
317             * Returns the value of the <code>defaultHqlOrderBy</code> property.
318             * 
319             * @return a {@link String}.
320             */
321            public final String getDefaultHqlOrderBy() {
322                    return defaultHqlOrderBy;
323            }
324    
325    }