View Javadoc
1   package net.sumaris.core.dao.technical.hibernate;
2   
3   /*-
4    * #%L
5    * SUMARiS :: Sumaris Core Shared
6    * $Id:$
7    * $HeadURL:$
8    * %%
9    * Copyright (C) 2018 SUMARiS Consortium
10   * %%
11   * This program is free software: you can redistribute it and/or modify
12   * it under the terms of the GNU General Public License as
13   * published by the Free Software Foundation, either version 3 of the
14   * License, or (at your option) any later version.
15   * 
16   * This program is distributed in the hope that it will be useful,
17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19   * GNU General Public License for more details.
20   * 
21   * You should have received a copy of the GNU General Public
22   * License along with this program.  If not, see
23   * <http://www.gnu.org/licenses/gpl-3.0.html>.
24   * #L%
25   */
26  
27  
28  import com.google.common.collect.Multimap;
29  import net.sumaris.core.config.SumarisConfiguration;
30  import net.sumaris.core.dao.technical.Daos;
31  import net.sumaris.core.dao.technical.model.IEntity;
32  import net.sumaris.core.dao.technical.model.IUpdateDateEntityBean;
33  import net.sumaris.core.exception.BadUpdateDateException;
34  import net.sumaris.core.exception.DataLockedException;
35  import net.sumaris.core.exception.SumarisTechnicalException;
36  import net.sumaris.core.util.Dates;
37  import org.apache.commons.lang3.StringUtils;
38  import org.hibernate.Session;
39  import org.hibernate.SessionFactory;
40  import org.hibernate.dialect.Dialect;
41  import org.nuiton.i18n.I18n;
42  import org.slf4j.Logger;
43  import org.slf4j.LoggerFactory;
44  import org.springframework.beans.factory.annotation.Autowired;
45  import org.springframework.dao.DataAccessResourceFailureException;
46  import org.springframework.dao.DataIntegrityViolationException;
47  
48  import javax.persistence.EntityManager;
49  import javax.persistence.LockModeType;
50  import javax.persistence.LockTimeoutException;
51  import javax.persistence.Query;
52  import javax.sql.DataSource;
53  import java.io.Serializable;
54  import java.math.BigInteger;
55  import java.sql.SQLException;
56  import java.sql.Timestamp;
57  import java.util.Collection;
58  import java.util.Date;
59  import java.util.Map;
60  import java.util.Objects;
61  
62  /**
63   * <p>HibernateDaoSupport class.</p>
64   *
65   */
66  public abstract class HibernateDaoSupport {
67  
68      /**
69       * Logger.
70       */
71      protected static final Logger logger =
72              LoggerFactory.getLogger(HibernateDaoSupport.class);
73  
74      private boolean debugEntityLoad;
75  
76      @Autowired
77      protected EntityManager entityManager;
78  
79      @Autowired
80      protected SumarisConfiguration config;
81  
82      @Autowired
83      private DataSource dataSource;
84  
85      /**
86       * <p>Constructor for HibernateDaoSupport.</p>
87       */
88      public HibernateDaoSupport() {
89          this.debugEntityLoad = SumarisConfiguration.getInstance().debugEntityLoad();
90      }
91  
92      /**
93       * <p>Setter for the field <code>entityManager</code>.</p>
94       *
95       * @param entityManager a {@link EntityManager} object.
96       */
97      protected void setEntityManager(EntityManager entityManager) {
98          this.entityManager = entityManager;
99      }
100 
101     /**
102      * @deprecated use EntityManager instead
103      * @param sf
104      */
105     @Deprecated
106     protected void setSessionFactory(SessionFactory sf) {
107         logger.warn("TODO: remove call to deprecated setSessionFactory()");
108     }
109 
110     /**
111      * <p>getEntityManager.</p>
112      *
113      * @return a {@link Session} object.
114      */
115     protected EntityManager getEntityManager() {
116         return entityManager;
117     }
118 
119 
120     protected Session getSession() {
121         return entityManager.unwrap(Session.class);
122     }
123 
124     /**
125      * <p>deleteAll.</p>
126      *
127      * @param entities a {@link Collection} object.
128      */
129     protected void deleteAll(Collection<?> entities) {
130         EntityManager entityManager = getEntityManager();
131         for (Object entity : entities) {
132             entityManager.remove(entity);
133         }
134     }
135 
136     /**
137      * <p>deleteAll.</p>
138      *
139      * @param entityClass a {@link Collection} object.
140      * @param identifier a {@link Serializable} object.
141      */
142     protected <T> void delete(Class<T> entityClass, Serializable identifier) {
143         EntityManager entityManager = getEntityManager();
144         T entity = entityManager.find(entityClass, identifier);
145         if (entity != null) {
146             entityManager.remove(entity);
147         }
148     }
149 
150     /**
151      * <p>load.</p>
152      *
153      * @param clazz a {@link Class} object.
154      * @param id a {@link Serializable} object.
155      * @param <T> a T object.
156      * @return a T object.
157      */
158     @SuppressWarnings("unchecked")
159     protected <T> T load(Class<? extends T> clazz, Serializable id) {
160 
161         if (debugEntityLoad) {
162             T load = entityManager.find(clazz, id);
163             if (load == null) {
164                 throw new DataIntegrityViolationException("Unable to load entity " + clazz.getName() + " with identifier '" + id + "': not found in database.");
165             }
166         }
167         return entityManager.unwrap(Session.class).load(clazz, id);
168     }
169 
170     /**
171      * <p>get.</p>
172      *
173      * @param clazz a {@link Class} object.
174      * @param id a {@link Serializable} object.
175      * @param <T> a T object.
176      * @return a T object.
177      */
178     @SuppressWarnings("unchecked")
179     protected <T> T get(Class<? extends T> clazz, Serializable id) {
180         return getEntityManager().find(clazz, id);
181     }
182 
183     /**
184      * <p>get.</p>
185      *
186      * @param clazz a {@link Class} object.
187      * @param id a {@link Serializable} object.
188      * @param lockModeType a {@link LockModeType} object.
189      * @param <T> a T object.
190      * @return a T object.
191      */
192     @SuppressWarnings("unchecked")
193     protected <T extends Serializable> T get(Class<? extends T> clazz, Serializable id, LockModeType lockModeType) {
194         T entity = entityManager.find(clazz, id);
195         entityManager.lock(entity, lockModeType);
196         return entity;
197     }
198 
199     /**
200      * <p>executeMultipleCountWithNotNullCondition.</p>
201      *
202      * @param columnNamesByTableNames a {@link Multimap} object.
203      * @param notNullConditionColumnNameByTableNames a {@link Map} object.
204      * @param source a int.
205      * @return a boolean.
206      */
207     protected boolean executeMultipleCountWithNotNullCondition(Multimap<String, String> columnNamesByTableNames, Map<String, String> notNullConditionColumnNameByTableNames, int source) {
208 
209         String countQueryString = "SELECT COUNT(*) FROM %s WHERE %s = %d";
210         return executeMultipleCount(countQueryString, columnNamesByTableNames, " AND %s IS NOT NULL", notNullConditionColumnNameByTableNames, source);
211     }
212 
213     /**
214      * <p>executeMultipleCount.</p>
215      *
216      * @param columnNamesByTableNames a {@link Multimap} object.
217      * @param source a int.
218      * @return a boolean.
219      */
220     protected boolean executeMultipleCount(Multimap<String, String> columnNamesByTableNames, int source) {
221 
222         String countQueryString = "SELECT COUNT(*) FROM %s WHERE %s = %d";
223         return executeMultipleCount(countQueryString, columnNamesByTableNames, null, null, source);
224     }
225 
226     /**
227      * <p>executeMultipleCount.</p>
228      *
229      * @param columnNamesByTableNames a {@link Multimap} object.
230      * @param source a {@link String} object.
231      * @return a boolean.
232      */
233     protected boolean executeMultipleCount(Multimap<String, String> columnNamesByTableNames, String source) {
234 
235         String countQueryString = "SELECT COUNT(*) FROM %s WHERE %s = '%s'";
236         return executeMultipleCount(countQueryString, columnNamesByTableNames, null, null, source);
237     }
238 
239     private boolean executeMultipleCount(
240             String countQueryString,
241             Multimap<String, String> columnNamesByTableNames,
242             String conditionQueryAppendix,
243             Map<String, String> conditionColumnNameByTableNames,
244             Object source) {
245 
246         String queryString;
247         Query query;
248         for (String tableName : columnNamesByTableNames.keySet()) {
249             Collection<String> columnNames = columnNamesByTableNames.get(tableName);
250             String conditionColumnName = conditionColumnNameByTableNames == null ? null : conditionColumnNameByTableNames.get(tableName);
251             for (String columnName : columnNames) {
252                 if (StringUtils.isNotBlank(conditionQueryAppendix) && StringUtils.isNotBlank(conditionColumnName)) {
253                     queryString = String.format(countQueryString.concat(conditionQueryAppendix), tableName, columnName, source, conditionColumnName);
254                 } else {
255                     queryString = String.format(countQueryString, tableName, columnName, source);
256                 }
257 
258                 query = entityManager.createNativeQuery(queryString);
259                 if (logger.isDebugEnabled()) {
260                     logger.debug(queryString);
261                 }
262                 BigInteger count = (BigInteger) query.getSingleResult();
263                 if (count.intValue() > 0) {
264                     return true;
265                 }
266             }
267         }
268         return false;
269     }
270 
271     /**
272      * <p>executeMultipleUpdate.</p>
273      *
274      * @param columnNamesByTableNames a {@link Multimap} object.
275      * @param sourceId a int.
276      * @param targetId a int.
277      */
278     protected void executeMultipleUpdate(Multimap<String, String> columnNamesByTableNames, int sourceId, int targetId) {
279 
280         String updateQueryString = "UPDATE %s SET %s = %d WHERE %s = %d";
281         executeMultipleUpdate(updateQueryString, columnNamesByTableNames, null, null, sourceId, targetId);
282     }
283 
284     /**
285      * <p>executeMultipleUpdateWithNullCondition.</p>
286      *
287      * @param columnNamesByTableNames a {@link Multimap} object.
288      * @param nullConditionColumnNameByTableNames a {@link Map} object.
289      * @param sourceId a int.
290      * @param targetId a int.
291      */
292     protected void executeMultipleUpdateWithNullCondition(Multimap<String, String> columnNamesByTableNames, Map<String, String> nullConditionColumnNameByTableNames, int sourceId, int targetId) {
293 
294         String updateQueryString = "UPDATE %s SET %s = %d WHERE %s = %d";
295         executeMultipleUpdate(updateQueryString, columnNamesByTableNames, " AND %s IS NULL", nullConditionColumnNameByTableNames, sourceId, targetId);
296     }
297 
298     /**
299      * <p>executeMultipleUpdate.</p>
300      *
301      * @param columnNamesByTableNames a {@link Multimap} object.
302      * @param sourceCode a {@link String} object.
303      * @param targetCode a {@link String} object.
304      */
305     protected void executeMultipleUpdate(Multimap<String, String> columnNamesByTableNames, String sourceCode, String targetCode) {
306 
307         String updateQueryString = "UPDATE %s SET %s = '%s' WHERE %s = '%s'";
308         executeMultipleUpdate(updateQueryString, columnNamesByTableNames, null, null, sourceCode, targetCode);
309     }
310 
311     private void executeMultipleUpdate(
312             String updateQueryString,
313             Multimap<String, String> columnNamesByTableNames,
314             String conditionQueryAppendix,
315             Map<String, String> conditionColumnNameByTableNames,
316             Object source,
317             Object target) {
318 
319         String queryString;
320         Query query;
321 
322         for (String tableName : columnNamesByTableNames.keySet()) {
323             Collection<String> columnNames = columnNamesByTableNames.get(tableName);
324             String conditionColumnName = conditionColumnNameByTableNames == null ? null : conditionColumnNameByTableNames.get(tableName);
325             for (String columnName : columnNames) {
326                 if (StringUtils.isNotBlank(conditionQueryAppendix) && StringUtils.isNotBlank(conditionColumnName)) {
327                     queryString = String.format(updateQueryString.concat(conditionQueryAppendix), tableName, columnName, target, columnName, source, conditionColumnName);
328                 } else {
329                     queryString = String.format(updateQueryString, tableName, columnName, target, columnName, source);
330                 }
331 
332                 query = entityManager.createNativeQuery(queryString);
333                 if (logger.isDebugEnabled()) {
334                     logger.debug(queryString);
335                 }
336                 query.executeUpdate();
337             }
338         }
339     }
340 
341     /**
342      * <p>getDatabaseCurrentTimestamp.</p>
343      *
344      * @return a {@link Timestamp} object.
345      */
346     protected Timestamp getDatabaseCurrentTimestamp() {
347         try {
348             final Dialect dialect = Dialect.getDialect(SumarisConfiguration.getInstance().getConnectionProperties());
349             final String sql = dialect.getCurrentTimestampSelectString();
350             Object r = Daos.sqlUniqueTimestamp(dataSource, sql);
351             return Daos.toTimestampFromJdbcResult(r);
352         }catch(DataAccessResourceFailureException | SQLException e) {
353             throw new SumarisTechnicalException(e);
354         }
355     }
356 
357 
358     protected String getTableName(String entityName) {
359 
360         return I18n.t("sumaris.persistence.table."+ entityName.substring(0,1).toLowerCase() + entityName.substring(1));
361     }
362 
363     protected void checkUpdateDateForUpdate(IUpdateDateEntityBean<?, ? extends Date> source,
364                                             IUpdateDateEntityBean<?, ? extends Date> entity) {
365         // Check update date
366         if (entity.getUpdateDate() != null) {
367             Timestamp serverUpdateDtNoMillisecond = Dates.resetMillisecond(entity.getUpdateDate());
368             Timestamp sourceUpdateDtNoMillisecond = Dates.resetMillisecond(source.getUpdateDate());
369             if (!Objects.equals(sourceUpdateDtNoMillisecond, serverUpdateDtNoMillisecond)) {
370                 throw new BadUpdateDateException(I18n.t("sumaris.persistence.error.badUpdateDate",
371                         getTableName(entity.getClass().getSimpleName()), source.getId(), serverUpdateDtNoMillisecond,
372                         sourceUpdateDtNoMillisecond));
373             }
374         }
375     }
376 
377     protected void lockForUpdate(IEntity<?> entity) {
378        lockForUpdate(entity, LockModeType.PESSIMISTIC_WRITE);
379     }
380 
381     protected void lockForUpdate(IEntity<?> entity, LockModeType modeType) {
382         // Lock entityName
383         try {
384             entityManager.lock(entity, modeType);
385         } catch (LockTimeoutException e) {
386             throw new DataLockedException(I18n.t("sumaris.persistence.error.locked",
387                     getTableName(entity.getClass().getSimpleName()), entity.getId()), e);
388         }
389     }
390 
391     protected void delete(IEntity<?> entity) {
392         entityManager.remove(entity);
393     }
394 
395 }