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.close();
059          entityManager = null;
060        }
061      }
062    
063      public void commit() {
064        if (entityManager != null && inTransaction) {
065          if (entityManager.isOpen()) {
066            if (entityManager.getTransaction().getRollbackOnly()) {
067              entityManager.getTransaction().rollback();
068            } else {
069              entityManager.getTransaction().commit();
070            }
071            entityManager.clear();
072            index = 0;
073          }
074          inTransaction = false;
075        }
076      }
077    
078      public void rollback() {
079        if (entityManager != null && inTransaction) {
080          entityManager.getTransaction().rollback();
081          inTransaction = false;
082        }
083      }
084    
085      public <T> T save(T model) {
086        startTransaction();
087        internalSave(model, true);
088        return model;
089      }
090    
091      public Object saveWithoutFlush(Object model) {
092        startTransaction();
093        internalSave(model, false);
094        return model;
095      }
096    
097      public boolean contains(Object model) {
098        startTransaction();
099        return entityManager.contains(model);
100      }
101    
102      public void save(Object... models) {
103        startTransaction();
104        for (Object model : models) {
105          save(model);
106        }
107      }
108    
109      private void internalSave(Object model, boolean flushIfNeeded) {
110        entityManager.persist(model);
111        if (flushIfNeeded && (++index % BATCH_SIZE == 0)) {
112          commit();
113        }
114      }
115    
116      public Object merge(Object model) {
117        startTransaction();
118        return entityManager.merge(model);
119      }
120    
121      public void remove(Object model) {
122        startTransaction();
123        entityManager.remove(model);
124        if (++index % BATCH_SIZE == 0) {
125          commit();
126        }
127      }
128    
129      public void removeWithoutFlush(Object model) {
130        startTransaction();
131        entityManager.remove(model);
132      }
133    
134      public <T> T reattach(Class<T> entityClass, Object primaryKey) {
135        startTransaction();
136        return entityManager.getReference(entityClass, primaryKey);
137      }
138    
139      private void startTransaction() {
140        if (!inTransaction) {
141          entityManager.getTransaction().begin();
142          inTransaction = true;
143        }
144      }
145    
146    
147      public Query createQuery(String hql) {
148        startTransaction();
149        return entityManager.createQuery(hql);
150      }
151    
152      public <T> T getSingleResult(Query query, T defaultValue) {
153        try {
154          return (T) query.getSingleResult();
155        } catch (NoResultException ex) {
156          return defaultValue;
157        }
158      }
159    
160      public <T> T getEntity(Class<T> entityClass, Object id) {
161        startTransaction();
162        return getEntityManager().find(entityClass, id);
163      }
164    
165      public <T> T getSingleResult(Class<T> entityClass, Object... criterias) {
166        try {
167          return getSingleResult(getQueryForCriterias(entityClass, true, criterias), (T) null);
168    
169        } catch (NonUniqueResultException ex) {
170          LoggerFactory.getLogger(JpaDatabaseSession.class).warn("NonUniqueResultException on entity {} with criterias : {}",
171              entityClass.getSimpleName(), StringUtils.join(criterias, ","));
172          throw ex;
173        }
174      }
175    
176      public <T> List<T> getResults(Class<T> entityClass, Object... criterias) {
177        return getQueryForCriterias(entityClass, true, criterias).getResultList();
178      }
179    
180      public <T> List<T> getResults(Class<T> entityClass) {
181        return getQueryForCriterias(entityClass, false, null).getResultList();
182      }
183    
184      private Query getQueryForCriterias(Class<?> entityClass, boolean raiseError, Object... criterias) {
185        if (criterias == null && raiseError) {
186          throw new IllegalStateException("criterias parameter must be provided");
187        }
188        startTransaction();
189        StringBuilder hql = new StringBuilder("SELECT o FROM ").append(entityClass.getSimpleName()).append(" o");
190        if (criterias != null) {
191          hql.append(" WHERE ");
192          Map<String, Object> mappedCriterias = new HashMap<String, Object>();
193          for (int i = 0; i < criterias.length; i += 2) {
194            mappedCriterias.put((String) criterias[i], criterias[i + 1]);
195          }
196          buildCriteriasHQL(hql, mappedCriterias);
197          Query query = getEntityManager().createQuery(hql.toString());
198    
199          for (Map.Entry<String, Object> entry : mappedCriterias.entrySet()) {
200            query.setParameter(entry.getKey(), entry.getValue());
201          }
202          return query;
203        }
204        return getEntityManager().createQuery(hql.toString());
205      }
206    
207      private void buildCriteriasHQL(StringBuilder hql, Map<String, Object> mappedCriterias) {
208        for (Iterator<String> i = mappedCriterias.keySet().iterator(); i.hasNext();) {
209          String criteria = i.next();
210          hql.append("o.").append(criteria).append("=:").append(criteria);
211          if (i.hasNext()) {
212            hql.append(" AND ");
213          }
214        }
215      }
216    
217    }