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.BatchComponent;
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 BatchComponent {
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 }