001    /*
002     * ----------------------------------------------------------------------
003     * Copyright (C) 2009 Enrique Lara (k957@68k.org)
004     *
005     * TinLizard is free software; you can redistribute it and/or
006     * modify it under the terms of the GNU Lesser General Public License
007     * as published by the Free Software Foundation; either version 3.0
008     * of the License, or (at your option) any later version.
009     *
010     * TinLizard is distributed in the hope that it will be useful,
011     * but WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013     * GNU Lesser General Public License for more details.
014     *
015     * You should have received a copy of the GNU Lesser General Public License
016     * along with TinLizard. If not, see http://www.gnu.org/licenses/.
017     * ----------------------------------------------------------------------
018     */
019    package tinlizard.dao.jpa;
020    
021    import tinlizard.model.CurrentUser;
022    import tinlizard.model.TinLizardConfig;
023    
024    import java.util.Collection;
025    import java.util.Date;
026    import java.util.Iterator;
027    import java.util.List;
028    
029    import javax.persistence.EntityExistsException;
030    import javax.persistence.EntityManager;
031    import javax.persistence.EntityManagerFactory;
032    import javax.persistence.EntityNotFoundException;
033    import javax.persistence.EntityTransaction;
034    import javax.persistence.NoResultException;
035    import javax.persistence.NonUniqueResultException;
036    import javax.persistence.Persistence;
037    import javax.persistence.Query;
038    
039    import org.apache.log4j.Logger;
040    import org.apache.lucene.analysis.standard.StandardAnalyzer;
041    import org.apache.lucene.queryParser.MultiFieldQueryParser;
042    
043    import org.hibernate.search.jpa.FullTextEntityManager;
044    import org.hibernate.search.jpa.Search;
045    
046    /**
047     * Dao to wrap/handle common JPA interactions.
048     */
049    public final class JpaDao {
050        private static JpaDao instance = null;
051        private static final ThreadLocal<EntityManager> THREAD_EM = new ThreadLocal<EntityManager>();
052        private static final Logger LOG = Logger.getLogger(JpaDao.class);
053        private final EntityManagerFactory emf;
054    
055        private JpaDao(final TinLizardConfig initializer) {
056            emf = Persistence.createEntityManagerFactory("TinLizardModel", initializer.getJpaProperties());
057        }
058    
059        public static synchronized void initialize(final TinLizardConfig initializer) {
060            if (instance == null) {
061                instance = new JpaDao(initializer);
062            }
063        }
064    
065        public static JpaDao getInstance() {
066            return instance;
067        }
068    
069        public EntityManager getEm() {
070            EntityManager em = THREAD_EM.get();
071    
072            if (em == null) {
073                em = emf.createEntityManager();
074                THREAD_EM.set(em);
075            }
076    
077            return em;
078        }
079    
080        void closeEm() {
081            EntityManager em = THREAD_EM.get();
082    
083            LOG.trace("closeEm");
084    
085            if (em != null) {
086                THREAD_EM.remove();
087    
088                if (em.isOpen()) {
089                    em.close();
090                }
091            }
092        }
093    
094        @SuppressWarnings("unchecked")
095        public void add(final Persistable obj) {
096            //XXX consider @EntityListeners http://www.javaworld.com/javaworld/jw-01-2008/jw-01-jpa1.html?page=4
097            obj.setCreated(new Date());
098            obj.setCreatedBy(CurrentUser.getUsername());
099    
100            EntityManager em = getEm();
101            EntityTransaction tx = em.getTransaction();
102            tx.begin();
103    
104            try {
105                em.persist(obj);
106    
107                tx.commit();
108            } catch (EntityExistsException e) {
109                handleError(tx, "Add Error", e);
110            } catch (Exception e) {
111                handleError(tx, "Add Error", e);
112            }
113        }
114    
115        @SuppressWarnings("unchecked")
116        public void update(final Persistable obj) {
117            obj.setLastModified(new Date());
118            obj.setLastModifiedBy(CurrentUser.getUsername());
119    
120            EntityManager em = getEm();
121            EntityTransaction tx = em.getTransaction();
122            tx.begin();
123    
124            try {
125                em.merge(obj);
126    
127                tx.commit();
128            } catch (Exception e) {
129                handleError(tx, "Update Error", e);
130            }
131        }
132    
133        public void delete(final Class<?> clazz, final Integer id) {
134            EntityManager em = getEm();
135            EntityTransaction tx = em.getTransaction();
136            tx.begin();
137    
138            try {
139                if (LOG.isDebugEnabled()) {
140                    LOG.debug("delete(" + clazz + ", " + id + ")");
141                }
142    
143                Query query = em.createQuery("delete from " + clazz.getSimpleName() + " o where o.id = :id");
144                query.setParameter("id", id);
145    
146                int deleted = query.executeUpdate();
147    
148                if (LOG.isDebugEnabled()) {
149                    LOG.debug("Deleted " + deleted + ".");
150                }
151    
152                tx.commit();
153            } catch (EntityNotFoundException e) {
154                handleInfo(tx, "Object " + clazz + "#" + id + " did not exist", e);
155            } catch (Exception e) {
156                handleError(tx, "Delete Error", e);
157            }
158        }
159    
160        @SuppressWarnings("unchecked")
161        public void index(final Persistable obj) {
162            EntityManager em = getEm();
163            FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(em);
164            EntityTransaction tx = em.getTransaction();
165            tx.begin();
166    
167            try {
168                fullTextEntityManager.index(obj);
169    
170                tx.commit();
171            } catch (Exception e) {
172                handleError(tx, "Index Error", e);
173            }
174        }
175    
176        @SuppressWarnings("unchecked")
177        public void indexAll(final Class<?extends Persistable> clazz) {
178            EntityManager em = getEm();
179            FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(em);
180            EntityTransaction tx = em.getTransaction();
181            tx.begin();
182    
183            try {
184                fullTextEntityManager.purgeAll(clazz);
185    
186                List objects = em.createQuery("select o from " + clazz.getSimpleName() + " o order by o.name").getResultList();
187    
188                for (Iterator iterator = objects.iterator(); iterator.hasNext();) {
189                    Object object = iterator.next();
190    
191                    fullTextEntityManager.index(object);
192                }
193    
194                tx.commit();
195            } catch (Exception e) {
196                handleError(tx, "IndexAll Error", e);
197            }
198        }
199    
200        public void handleInfo(final EntityTransaction tx, final String msg, final Exception e) {
201            rollbackTransaction(tx);
202            LOG.info(msg, e);
203        }
204    
205        public void handleError(final EntityTransaction tx, final String msg, final Exception e) {
206            rollbackTransaction(tx);
207    
208            closeEm();
209            throw new RuntimeException(msg, e);
210        }
211    
212        private void rollbackTransaction(final EntityTransaction tx) {
213            if (tx.isActive()) {
214                tx.rollback();
215            }
216        }
217    
218        @SuppressWarnings("unchecked")
219        public <T> List<T> findAll(final Class<T> clazz) {
220            List<T> rval = null;
221    
222            EntityManager em = getEm();
223            EntityTransaction tx = em.getTransaction();
224            tx.begin();
225    
226            try {
227                rval = em.createQuery("select o from " + clazz.getSimpleName() + " o order by o.name").getResultList();
228    
229                tx.commit();
230            } catch (Exception e) {
231                handleError(tx, "FindAll Error", e);
232            }
233    
234            return rval;
235        }
236    
237        public <T> T findByPrimaryKey(final Class<T> clazz, final Integer id) {
238            T rval = null;
239            EntityManager em = getEm();
240            EntityTransaction tx = em.getTransaction();
241            tx.begin();
242    
243            try {
244                if (LOG.isDebugEnabled()) {
245                    LOG.debug("findByPrimaryKey(" + clazz + ", " + id + ")");
246                }
247    
248                rval = em.find(clazz, id);
249    
250                tx.commit();
251            } catch (Exception e) {
252                handleError(tx, "FindByPrimaryKey Error", e);
253            }
254    
255            return rval;
256        }
257    
258        public void flush() {
259            EntityManager em = getEm();
260            EntityTransaction tx = em.getTransaction();
261            tx.begin();
262    
263            try {
264                em.flush();
265    
266                tx.commit();
267            } catch (Exception e) {
268                handleError(tx, "Flush Error", e);
269            }
270        }
271    
272        @SuppressWarnings("unchecked")
273        public <T> Collection<T> findByNamedQuery(final Class<T> clazz, final String name, final Object... params) {
274            Collection<T> rval = null;
275    
276            EntityManager em = getEm();
277            EntityTransaction tx = em.getTransaction();
278            tx.begin();
279    
280            try {
281                Query q = getEm().createNamedQuery(name);
282    
283                for (int i = 0; i < params.length; i++) {
284                    q.setParameter(i + 1, params[i]);
285                }
286    
287                rval = q.getResultList();
288                tx.commit();
289            } catch (Exception e) {
290                handleError(tx, "FindByNamedQuery Error", e);
291            }
292    
293            return rval;
294        }
295    
296        @SuppressWarnings("unchecked")
297        public <T> T findSingleByNamedQuery(final Class<T> clazz, final String name, final Object... params) {
298            T rval = null;
299    
300            EntityManager em = getEm();
301            EntityTransaction tx = em.getTransaction();
302            tx.begin();
303    
304            try {
305                Query q = getEm().createNamedQuery(name);
306    
307                for (int i = 0; i < params.length; i++) {
308                    q.setParameter(i + 1, params[i]);
309                }
310    
311                rval = (T) q.getSingleResult();
312    
313                tx.commit();
314            } catch (EntityNotFoundException e) {
315                rollbackTransaction(tx);
316                closeEm();
317            } catch (NoResultException e) {
318                rollbackTransaction(tx);
319                closeEm();
320            } catch (NonUniqueResultException e) {
321                handleError(tx, "FindSingleByNamedQuery Error", e);
322            } catch (Exception e) {
323                handleError(tx, "FindSingleByNamedQuery Error", e);
324            }
325    
326            return rval;
327        }
328    
329        @SuppressWarnings("unchecked")
330        public <T> Collection<T> findByTextSearch(final Class<T> clazz, final String query, final String... fields) {
331            Collection<T> rval = null;
332    
333            EntityManager em = getEm();
334            FullTextEntityManager fullTextEntityManager = org.hibernate.search.jpa.Search.getFullTextEntityManager(em);
335            EntityTransaction tx = em.getTransaction();
336            tx.begin();
337    
338            try {
339                // create native Lucene query               
340                MultiFieldQueryParser parser = new MultiFieldQueryParser(fields, new StandardAnalyzer());
341                org.apache.lucene.search.Query luceneQuery = parser.parse(query);
342    
343                // wrap Lucene query in a javax.persistence.Query
344                javax.persistence.Query persistenceQuery = fullTextEntityManager.createFullTextQuery(luceneQuery, clazz);
345    
346                // execute search
347                rval = persistenceQuery.getResultList();
348    
349                tx.commit();
350            } catch (Exception e) {
351                handleError(tx, "FindByTextSearch Error", e);
352            }
353    
354            return rval;
355        }
356    }