001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2009 SonarSource SA
004     * mailto:contact AT sonarsource DOT com
005     *
006     * Sonar is free software; you can redistribute it and/or
007     * modify it under the terms of the GNU Lesser General Public
008     * License as published by the Free Software Foundation; either
009     * version 3 of the License, or (at your option) any later version.
010     *
011     * Sonar is distributed in the hope that it will be useful,
012     * but WITHOUT ANY WARRANTY; without even the implied warranty of
013     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014     * Lesser General Public License for more details.
015     *
016     * You should have received a copy of the GNU Lesser General Public
017     * License along with Sonar; if not, write to the Free Software
018     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
019     */
020    package org.sonar.jpa.session;
021    
022    import org.apache.commons.lang.StringUtils;
023    import org.slf4j.LoggerFactory;
024    import org.sonar.api.database.DatabaseSession;
025    
026    import javax.persistence.EntityManager;
027    import javax.persistence.NoResultException;
028    import javax.persistence.NonUniqueResultException;
029    import javax.persistence.Query;
030    import java.util.HashMap;
031    import java.util.Iterator;
032    import java.util.List;
033    import java.util.Map;
034    
035    public class JpaDatabaseSession extends DatabaseSession {
036    
037      private final DatabaseConnector connector;
038      private EntityManager entityManager = null;
039      private int index = 0;
040      private boolean inTransaction = false;
041    
042      public JpaDatabaseSession(DatabaseConnector connector) {
043        this.connector = connector;
044      }
045    
046      public EntityManager getEntityManager() {
047        return entityManager;
048      }
049    
050      public void start() {
051        entityManager = connector.createEntityManager();
052        index = 0;
053      }
054    
055      public void stop() {
056        commit();
057        if (entityManager != null && entityManager.isOpen()) {
058          entityManager.clear();
059          entityManager.close();
060          entityManager = null;
061        }
062      }
063    
064      public void commit() {
065        if (entityManager != null && inTransaction) {
066          if (entityManager.isOpen()) {
067            if (entityManager.getTransaction().getRollbackOnly()) {
068              entityManager.getTransaction().rollback();
069            } else {
070              entityManager.getTransaction().commit();
071            }
072          }
073          inTransaction = false;
074          index = 0;
075        }
076      }
077    
078      public void rollback() {
079        if (entityManager != null && inTransaction) {
080          entityManager.getTransaction().rollback();
081          inTransaction = false;
082          index = 0;
083        }
084      }
085    
086      public <T> T save(T model) {
087        startTransaction();
088        internalSave(model, true);
089        return model;
090      }
091    
092      public Object saveWithoutFlush(Object model) {
093        startTransaction();
094        internalSave(model, false);
095        return model;
096      }
097    
098      public boolean contains(Object model) {
099        startTransaction();
100        return entityManager.contains(model);
101      }
102    
103      public void save(Object... models) {
104        startTransaction();
105        for (Object model : models) {
106          save(model);
107        }
108      }
109    
110      private void internalSave(Object model, boolean flushIfNeeded) {
111        entityManager.persist(model);
112        if (flushIfNeeded && (++index % BATCH_SIZE == 0)) {
113          flush();
114        }
115      }
116    
117      public Object merge(Object model) {
118        startTransaction();
119        return entityManager.merge(model);
120      }
121    
122      public void remove(Object model) {
123        startTransaction();
124        entityManager.remove(model);
125        if (++index % BATCH_SIZE == 0) {
126          flush();
127        }
128      }
129    
130      public void removeWithoutFlush(Object model) {
131        startTransaction();
132        entityManager.remove(model);
133      }
134    
135      public <T> T reattach(Class<T> entityClass, Object primaryKey) {
136        startTransaction();
137        return entityManager.getReference(entityClass, primaryKey);
138      }
139    
140      private void startTransaction() {
141        if (!inTransaction) {
142          entityManager.getTransaction().begin();
143          inTransaction = true;
144        }
145      }
146    
147      public void flush() {
148        entityManager.flush();
149        entityManager.clear();
150      }
151    
152      public Query createQuery(String hql) {
153        startTransaction();
154        return entityManager.createQuery(hql);
155      }
156    
157      public <T> T getSingleResult(Query query, T defaultValue) {
158        try {
159          return (T) query.getSingleResult();
160        } catch (NoResultException ex) {
161          return defaultValue;
162        }
163      }
164    
165      public <T> T getEntity(Class<T> entityClass, Object id) {
166        startTransaction();
167        return getEntityManager().find(entityClass, id);
168      }
169    
170      public <T> T getSingleResult(Class<T> entityClass, Object... criterias) {
171        try {
172          return getSingleResult(getQueryForCriterias(entityClass, true, criterias), (T) null);
173    
174        } catch (NonUniqueResultException ex) {
175          LoggerFactory.getLogger(JpaDatabaseSession.class).warn("NonUniqueResultException on entity {} with criterias : {}",
176              entityClass.getSimpleName(), StringUtils.join(criterias, ","));
177          throw ex;
178        }
179      }
180    
181      public <T> List<T> getResults(Class<T> entityClass, Object... criterias) {
182        return getQueryForCriterias(entityClass, true, criterias).getResultList();
183      }
184    
185      public <T> List<T> getResults(Class<T> entityClass) {
186        return getQueryForCriterias(entityClass, false, null).getResultList();
187      }
188    
189      private Query getQueryForCriterias(Class<?> entityClass, boolean raiseError, Object... criterias) {
190        if (criterias == null && raiseError) {
191          throw new IllegalStateException("criterias parameter must be provided");
192        }
193        startTransaction();
194        StringBuilder hql = new StringBuilder("SELECT o FROM ").append(entityClass.getSimpleName()).append(" o");
195        if (criterias != null) {
196          hql.append(" WHERE ");
197          Map<String, Object> mappedCriterias = new HashMap<String, Object>();
198          for (int i = 0; i < criterias.length; i += 2) {
199            mappedCriterias.put((String) criterias[i], criterias[i + 1]);
200          }
201          buildCriteriasHQL(hql, mappedCriterias);
202          Query query = getEntityManager().createQuery(hql.toString());
203    
204          for (Map.Entry<String, Object> entry : mappedCriterias.entrySet()) {
205            query.setParameter(entry.getKey(), entry.getValue());
206          }
207          return query;
208        }
209        return getEntityManager().createQuery(hql.toString());
210      }
211    
212      private void buildCriteriasHQL(StringBuilder hql, Map<String, Object> mappedCriterias) {
213        for (Iterator<String> i = mappedCriterias.keySet().iterator(); i.hasNext();) {
214          String criteria = i.next();
215          hql.append("o.").append(criteria).append("=:").append(criteria);
216          if (i.hasNext()) {
217            hql.append(" AND ");
218          }
219        }
220      }
221    
222    }