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.api.database;
021    
022    import org.apache.commons.lang.StringUtils;
023    import org.slf4j.LoggerFactory;
024    import org.sonar.api.BatchExtension;
025    
026    import java.util.HashMap;
027    import java.util.Iterator;
028    import java.util.List;
029    import java.util.Map;
030    import javax.persistence.EntityManager;
031    import javax.persistence.NoResultException;
032    import javax.persistence.NonUniqueResultException;
033    import javax.persistence.Query;
034    
035    /**
036     * @since 1.10
037     */
038    public class DatabaseSession implements BatchExtension {
039    
040      // IMPORTANT : this value must be the same than the property
041      // hibernate.jdbc.batch_size from /META-INF/persistence.xml :
042      public static final int BATCH_SIZE = 30;
043    
044      private final DatabaseConnector connector;
045      private EntityManager entityManager = null;
046      private int index = 0;
047      private boolean inTransaction = false;
048    
049      public DatabaseSession(DatabaseConnector connector) {
050        this.connector = connector;
051      }
052    
053      public EntityManager getEntityManager() {
054        return entityManager;
055      }
056    
057      public void start() {
058        entityManager = connector.createEntityManager();
059        index = 0;
060      }
061    
062      public void stop() {
063        commit();
064        if (entityManager != null && entityManager.isOpen()) {
065          entityManager.clear();
066          entityManager.close();
067          entityManager = null;
068        }
069      }
070    
071      public void commit() {
072        if (entityManager != null && inTransaction) {
073          if (entityManager.isOpen()) {
074            if (entityManager.getTransaction().getRollbackOnly()) {
075              entityManager.getTransaction().rollback();
076            } else {
077              entityManager.getTransaction().commit();
078            }
079          }
080          inTransaction = false;
081          index = 0;
082        }
083      }
084    
085      public void rollback() {
086        if (entityManager != null && inTransaction) {
087          entityManager.getTransaction().rollback();
088          inTransaction = false;
089          index = 0;
090        }
091      }
092    
093      public <T> T save(T model) {
094        startTransaction();
095        internalSave(model, true);
096        return model;
097      }
098    
099      public Object saveWithoutFlush(Object model) {
100        startTransaction();
101        internalSave(model, false);
102        return model;
103      }
104    
105      public boolean contains(Object model) {
106        startTransaction();
107        return entityManager.contains(model);
108      }
109    
110      public void save(Object... models) {
111        startTransaction();
112        for (Object model : models) {
113          save(model);
114        }
115      }
116    
117      private void internalSave(Object model, boolean flushIfNeeded) {
118        entityManager.persist(model);
119        if (flushIfNeeded && (++index % BATCH_SIZE == 0)) {
120          flush();
121        }
122      }
123    
124      public Object merge(Object model) {
125        startTransaction();
126        return entityManager.merge(model);
127      }
128    
129      public void remove(Object model) {
130        startTransaction();
131        entityManager.remove(model);
132        if (++index % BATCH_SIZE == 0) {
133          flush();
134        }
135      }
136    
137      public <T> T reattach(Class<T> entityClass, Object primaryKey) {
138        startTransaction();
139        return entityManager.getReference(entityClass, primaryKey);
140      }
141    
142      private void startTransaction() {
143        if (!inTransaction) {
144          entityManager.getTransaction().begin();
145          inTransaction = true;
146        }
147      }
148    
149      private void flush() {
150        entityManager.flush();
151        entityManager.clear();
152      }
153    
154      public Query createQuery(String hql) {
155        startTransaction();
156        return entityManager.createQuery(hql);
157      }
158    
159      public <T> T getSingleResult(Query query, T defaultValue) {
160        try {
161          return (T) query.getSingleResult();
162        } catch (NoResultException ex) {
163          return defaultValue;
164        }
165      }
166    
167      public <T> T getEntity(Class<T> entityClass, Object id) {
168        startTransaction();
169        return getEntityManager().find(entityClass, id);
170      }
171    
172      public <T> T getSingleResult(Class<T> entityClass, Object... criterias) {
173        try {
174          return getSingleResult(getQueryForCriterias(entityClass, true, criterias), (T) null);
175        } catch (NonUniqueResultException ex) {
176          LoggerFactory.getLogger(DatabaseSession.class).warn("NonUniqueResultException on entity {} with criterias : {}",
177              entityClass.getSimpleName(), StringUtils.join(criterias, ","));
178          throw ex;
179        }
180      }
181    
182      public <T> List<T> getResults(Class<T> entityClass, Object... criterias) {
183        return getQueryForCriterias(entityClass, true, criterias).getResultList();
184      }
185    
186      public <T> List<T> getResults(Class<T> entityClass) {
187        return getQueryForCriterias(entityClass, false, null).getResultList();
188      }
189    
190      private Query getQueryForCriterias(Class<?> entityClass, boolean raiseError, Object... criterias) {
191        if (criterias == null && raiseError) {
192          throw new IllegalStateException("criterias parameter must be provided");
193        }
194        startTransaction();
195        StringBuilder hql = new StringBuilder("SELECT o FROM ").append(entityClass.getSimpleName()).append(" o");
196        if (criterias != null) {
197          hql.append(" WHERE ");
198          Map<String, Object> mappedCriterias = new HashMap<String, Object>();
199          for (int i = 0; i < criterias.length; i += 2) {
200            mappedCriterias.put((String) criterias[i], criterias[i + 1]);
201          }
202          buildCriteriasHQL(hql, mappedCriterias);
203          Query query = getEntityManager().createQuery(hql.toString());
204    
205          for (Map.Entry<String, Object> entry : mappedCriterias.entrySet()) {
206            query.setParameter(entry.getKey(), entry.getValue());
207          }
208          return query;
209        }
210        return getEntityManager().createQuery(hql.toString());
211      }
212    
213      private void buildCriteriasHQL(StringBuilder hql, Map<String, Object> mappedCriterias) {
214        for (Iterator<String> i = mappedCriterias.keySet().iterator(); i.hasNext();) {
215          String criteria = i.next();
216          hql.append("o.").append(criteria).append("=:").append(criteria);
217          if (i.hasNext()) {
218            hql.append(" AND ");
219          }
220        }
221      }
222    
223    }