View Javadoc
1   /*
2    * This file is part of Waarp Project (named also Waarp or GG).
3    *
4    *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
5    *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
6    * individual contributors.
7    *
8    *  All Waarp Project is free software: you can redistribute it and/or
9    * modify it under the terms of the GNU General Public License as published by
10   * the Free Software Foundation, either version 3 of the License, or (at your
11   * option) any later version.
12   *
13   * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
14   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15   * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16   *
17   *  You should have received a copy of the GNU General Public License along with
18   * Waarp . If not, see <http://www.gnu.org/licenses/>.
19   */
20  
21  package org.waarp.openr66.dao.database;
22  
23  import org.waarp.common.database.exception.WaarpDatabaseSqlException;
24  import org.waarp.common.logging.WaarpLogger;
25  import org.waarp.common.logging.WaarpLoggerFactory;
26  import org.waarp.common.lru.SynchronizedLruCache;
27  import org.waarp.openr66.dao.AbstractDAO;
28  import org.waarp.openr66.dao.DAOFactory;
29  import org.waarp.openr66.dao.Filter;
30  import org.waarp.openr66.dao.database.DBDAOFactory.FakeConnection;
31  import org.waarp.openr66.dao.exception.DAOConnectionException;
32  import org.waarp.openr66.dao.exception.DAONoDataException;
33  
34  import java.sql.Connection;
35  import java.sql.PreparedStatement;
36  import java.sql.ResultSet;
37  import java.sql.SQLException;
38  import java.sql.Statement;
39  import java.util.ArrayList;
40  import java.util.List;
41  
42  public abstract class StatementExecutor<E> implements AbstractDAO<E> {
43  
44    private static final WaarpLogger logger =
45        WaarpLoggerFactory.getLogger(StatementExecutor.class);
46    protected static final String WHERE = " WHERE ";
47    protected static final String PARAMETER = " = ?";
48    protected static final String PARAMETER_COMMA = " = ?, ";
49    protected static final String SQL_COUNT_ALL_PREFIX =
50        "SELECT COUNT(*) AS total FROM ";
51    protected Connection connection;
52  
53    protected abstract boolean isDbTransfer();
54  
55    public abstract E getFromResultSet(ResultSet set)
56        throws SQLException, DAOConnectionException;
57  
58    protected abstract boolean isCachedEnable();
59  
60    protected SynchronizedLruCache<String, E> getCache() {
61      return null;
62    }
63  
64    protected final void clearCache() {
65      if (isCachedEnable()) {
66        getCache().clear();
67      }
68    }
69  
70    protected final void addToCache(final String key, final E elt) {
71      if (isCachedEnable()) {
72        getCache().put(key, elt);
73      }
74    }
75  
76    protected final E getFromCache(final String key) {
77      if (isCachedEnable()) {
78        final E value = getCache().get(key);
79        if (value != null) {
80          getCache().updateTtl(key);
81        }
82        return value;
83      }
84      return null;
85    }
86  
87    protected final void removeFromCache(final String key) {
88      if (isCachedEnable()) {
89        getCache().remove(key);
90      }
91    }
92  
93    protected final boolean isInCache(final String key) {
94      if (isCachedEnable()) {
95        return getCache().contains(key);
96      }
97      return false;
98    }
99  
100   protected StatementExecutor(final Connection con) {
101     connection = con;
102   }
103 
104   public Connection getConnection() {
105     return connection;
106   }
107 
108   protected final void setParameters(final PreparedStatement stm,
109                                      final Object... values)
110       throws SQLException {
111     if (values == null) {
112       return;
113     }
114     for (int i = 0; i < values.length; i++) {
115       stm.setObject(i + 1, values[i]);
116     }
117   }
118 
119   protected final void executeUpdate(final PreparedStatement stm)
120       throws SQLException {
121     final int res;
122     res = stm.executeUpdate();
123     if (res < 1) {
124       logger.error("Update failed, no record updated.");
125       //FIXME should be throw new SQLDataException("Update failed, no record
126       // updated.");
127     } else {
128       logger.debug("{} records updated.", res);
129     }
130   }
131 
132   protected final int executeAction(final PreparedStatement stm)
133       throws SQLException {
134     return stm.executeUpdate();
135   }
136 
137   protected final ResultSet executeQuery(final PreparedStatement stm)
138       throws SQLException {
139     return stm.executeQuery();
140   }
141 
142   protected final void closeStatement(final Statement stm) {
143     if (stm == null) {
144       return;
145     }
146     try {
147       stm.close();
148     } catch (final SQLException e) {
149       logger.warn("An error occurs while closing the statement." + " : {}",
150                   e.getMessage());
151     }
152   }
153 
154   protected final void closeResultSet(final ResultSet rs) {
155     if (rs == null) {
156       return;
157     }
158     try {
159       rs.close();
160     } catch (final SQLException e) {
161       logger.warn("An error occurs while closing the resultSet." + " : {}",
162                   e.getMessage());
163     }
164   }
165 
166   public final void close() {
167     try {
168       connection.close();
169     } catch (final SQLException e) {
170       logger.warn("Cannot properly close the database connection" + " : {}",
171                   e.getMessage());
172     }
173   }
174 
175   protected abstract String getId(E e1);
176 
177   protected abstract String getTable();
178 
179   protected abstract String getSelectRequest();
180 
181   protected abstract String getCountRequest();
182 
183   protected abstract String getGetAllRequest();
184 
185   protected abstract String getExistRequest();
186 
187   protected abstract Object[] getInsertValues(E e1)
188       throws WaarpDatabaseSqlException;
189 
190   protected abstract String getInsertRequest();
191 
192   protected abstract Object[] getUpdateValues(E e1)
193       throws WaarpDatabaseSqlException;
194 
195   protected abstract String getUpdateRequest();
196 
197   protected abstract String getDeleteRequest();
198 
199   protected abstract String getDeleteAllRequest();
200 
201   @Override
202   public void delete(final E e1)
203       throws DAOConnectionException, DAONoDataException {
204     if (isCachedEnable()) {
205       // Need to check since all does not accept getId
206       removeFromCache(getId(e1));
207     }
208     PreparedStatement stm = null;
209     try {
210       stm = connection.prepareStatement(getDeleteRequest());
211       setParameters(stm, getId(e1));
212       try {
213         executeAction(stm);
214       } catch (final SQLException e2) {
215         throw new DAONoDataException(e2);
216       }
217     } catch (final SQLException e) {
218       throw new DAOConnectionException(e);
219     } finally {
220       closeStatement(stm);
221     }
222   }
223 
224   @Override
225   public final void deleteAll() throws DAOConnectionException {
226     clearCache();
227     PreparedStatement stm = null;
228     try {
229       stm = connection.prepareStatement(getDeleteAllRequest());
230       executeAction(stm);
231     } catch (final SQLException e) {
232       throw new DAOConnectionException(e);
233     } finally {
234       closeStatement(stm);
235     }
236   }
237 
238   protected final String prepareDeleteQuery(final List<Filter> filters,
239                                             final Object[] params) {
240     final StringBuilder query = new StringBuilder(getDeleteAllRequest());
241     return getFullRequest(filters, params, query);
242   }
243 
244   private final String getFullRequest(final List<Filter> filters,
245                                       final Object[] params,
246                                       final StringBuilder query) {
247     if (filters.isEmpty()) {
248       return query.toString();
249     }
250     query.append(WHERE);
251     String prefix = "";
252     int i = 0;
253     for (final Filter filter : filters) {
254       query.append(prefix);
255       if (filter.nbAdditionnalParams() > 1) {
256         final Object[] objects = (Object[]) filter.append(query);
257         for (final Object o : objects) {
258           params[i++] = o;
259         }
260       } else if (filter.nbAdditionnalParams() == 1) {
261         params[i] = filter.append(query);
262         i++;
263       } else {
264         filter.append(query);
265       }
266       prefix = " AND ";
267     }
268     return query.toString();
269   }
270 
271   /**
272    * Delete all items according to filter
273    *
274    * @param filters
275    *
276    * @return the number of deleted items
277    *
278    * @throws DAOConnectionException
279    */
280   public final int delete(final List<Filter> filters)
281       throws DAOConnectionException {
282     final ArrayList<E> es = new ArrayList<E>();
283     // Create the SQL query
284     final Object[] params = prepareFindParams(filters);
285     final String query = prepareDeleteQuery(filters, params);
286     // Execute query
287     PreparedStatement stm = null;
288     try {
289       stm = connection.prepareStatement(query);
290       setParameters(stm, params);
291       return executeAction(stm);
292     } catch (final SQLException e) {
293       throw new DAOConnectionException(e);
294     } finally {
295       closeStatement(stm);
296     }
297   }
298 
299   @Override
300   public final List<E> getAll() throws DAOConnectionException {
301     final ArrayList<E> es = new ArrayList<E>();
302     PreparedStatement stm = null;
303     ResultSet res = null;
304     try {
305       stm = connection.prepareStatement(getGetAllRequest());
306       res = executeQuery(stm);
307       while (res.next()) {
308         es.add(getFromResultSet(res));
309       }
310     } catch (final SQLException e) {
311       throw new DAOConnectionException(e);
312     } finally {
313       closeResultSet(res);
314       closeStatement(stm);
315     }
316     return es;
317   }
318 
319   protected final String prepareFindQuery(final List<Filter> filters,
320                                           final Object[] params) {
321     final StringBuilder query = new StringBuilder(getGetAllRequest());
322     return getFullRequest(filters, params, query);
323   }
324 
325   protected final Object[] prepareFindParams(final List<Filter> filters) {
326     Object[] params = new Object[0];
327     if (filters != null) {
328       int len = filters.size();
329       for (final Filter filter : filters) {
330         // Already got 1
331         len += filter.nbAdditionnalParams() - 1;
332       }
333       params = new Object[len];
334     }
335     return params;
336   }
337 
338   @Override
339   public final List<E> find(final List<Filter> filters)
340       throws DAOConnectionException {
341     return find(filters, null, false, -1);
342   }
343 
344   @Override
345   public final List<E> find(final List<Filter> filters, final int limit)
346       throws DAOConnectionException {
347     return find(filters, null, false, limit);
348   }
349 
350   @Override
351   public final List<E> find(final List<Filter> filters, final String field,
352                             final boolean asc) throws DAOConnectionException {
353     return find(filters, field, asc, -1);
354   }
355 
356   @Override
357   public final List<E> find(final List<Filter> filters, final String field,
358                             final boolean asc, final int limit)
359       throws DAOConnectionException {
360     return find(filters, field, asc, limit, -1);
361   }
362 
363   @Override
364   public final List<E> find(final List<Filter> filters, final String field,
365                             final boolean asc, final int limit,
366                             final int offset) throws DAOConnectionException {
367     final ArrayList<E> es = new ArrayList<E>();
368     // Create the SQL query
369     final Object[] params = prepareFindParams(filters);
370     final StringBuilder query =
371         new StringBuilder(prepareFindQuery(filters, params));
372     // Execute query
373     PreparedStatement stm = null;
374     ResultSet res = null;
375     try {
376       if (field != null) {
377         query.append(" ORDER BY ").append(field);
378         if (!asc) {
379           query.append(" DESC ");
380         }
381       }
382       String squery = query.toString();
383       if (limit > 0 || offset > 0) {
384         squery =
385             DAOFactory.getInstance().getLimitRequest(squery, limit, offset);
386       }
387       logger.debug("DEBUG {} {} {}", squery,
388                    params != null && params.length > 0? params[0] : "No first",
389                    params.length >= 2? params[1] : " No 2nd");
390       stm = connection.prepareStatement(squery);
391       setParameters(stm, params);
392       res = executeQuery(stm);
393       while (res.next()) {
394         es.add(getFromResultSet(res));
395       }
396     } catch (final SQLException e) {
397       throw new DAOConnectionException(e);
398     } finally {
399       closeResultSet(res);
400       closeStatement(stm);
401     }
402     return es;
403   }
404 
405   @Override
406   public final void update(final List<Filter> filters, final String fieldToSet)
407       throws DAOConnectionException {
408     final ArrayList<E> es = new ArrayList<E>();
409     // Create the SQL query
410     final Object[] params = prepareFindParams(filters);
411     if (isCachedEnable()) {
412       final String query = prepareFindQuery(filters, params);
413       // Execute query
414       PreparedStatement stm = null;
415       ResultSet res = null;
416       try {
417         stm = connection.prepareStatement(query);
418         setParameters(stm, params);
419         res = executeQuery(stm);
420         while (res.next()) {
421           final E e = getFromResultSet(res);
422           removeFromCache(getId(e));
423         }
424       } catch (final SQLException e) {
425         throw new DAOConnectionException(e);
426       } finally {
427         closeResultSet(res);
428         closeStatement(stm);
429       }
430     }
431     final StringBuilder query = new StringBuilder("UPDATE " + getTable());
432     query.append(" SET ").append(fieldToSet);
433     final String squery = getFullRequest(filters, params, query);
434     PreparedStatement stm = null;
435     try {
436       stm = connection.prepareStatement(squery);
437       setParameters(stm, params);
438       executeUpdate(stm);
439     } catch (final SQLException e) {
440       throw new DAOConnectionException(e);
441     } finally {
442       closeStatement(stm);
443     }
444   }
445 
446   protected final String prepareCountQuery(final List<Filter> filters,
447                                            final Object[] params) {
448     final StringBuilder query = new StringBuilder(getCountRequest());
449     return getFullRequest(filters, params, query);
450   }
451 
452   @Override
453   public final long count(final List<Filter> filters)
454       throws DAOConnectionException {
455     // Create the SQL query
456     final Object[] params = prepareFindParams(filters);
457     final String query = prepareCountQuery(filters, params);
458     // Execute query
459     PreparedStatement stm = null;
460     ResultSet res = null;
461     long total = -1;
462     try {
463       stm = connection.prepareStatement(query);
464       setParameters(stm, params);
465       res = executeQuery(stm);
466       if (res.next()) {
467         total = res.getLong("total");
468       }
469     } catch (final SQLException e) {
470       throw new DAOConnectionException(e);
471     } finally {
472       closeResultSet(res);
473       closeStatement(stm);
474     }
475     if (total < 0) {
476       throw new DAOConnectionException("Count cannot be retrieved");
477     }
478     return total;
479   }
480 
481   @Override
482   public final boolean exist(final String id) throws DAOConnectionException {
483     if (isDbTransfer()) {
484       throw new UnsupportedOperationException();
485     }
486     if (isInCache(id)) {
487       return true;
488     }
489     if (connection instanceof FakeConnection) {
490       try {
491         connection = ((FakeConnection) connection).getRealConnection();
492       } catch (final SQLException throwables) {
493         throw new DAOConnectionException(throwables);
494       }
495     }
496     PreparedStatement stm = null;
497     ResultSet res = null;
498     try {
499       stm = connection.prepareStatement(getExistRequest());
500       setParameters(stm, id);
501       res = executeQuery(stm);
502       return res.next();
503     } catch (final SQLException e) {
504       throw new DAOConnectionException(e);
505     } finally {
506       closeResultSet(res);
507       closeStatement(stm);
508     }
509   }
510 
511   @Override
512   public final E select(final String id)
513       throws DAOConnectionException, DAONoDataException {
514     if (isDbTransfer()) {
515       throw new UnsupportedOperationException();
516     }
517     if (isCachedEnable()) {
518       final E found = getFromCache(id);
519       if (found != null) {
520         return found;
521       }
522     }
523     if (connection instanceof FakeConnection) {
524       try {
525         connection = ((FakeConnection) connection).getRealConnection();
526       } catch (final SQLException throwables) {
527         throw new DAOConnectionException(throwables);
528       }
529     }
530     PreparedStatement stm = null;
531     ResultSet res = null;
532     try {
533       stm = connection.prepareStatement(getSelectRequest());
534       setParameters(stm, id);
535       res = executeQuery(stm);
536       if (res.next()) {
537         final E found = getFromResultSet(res);
538         addToCache(id, found);
539         return found;
540       } else {
541         throw new DAONoDataException("No " + getClass().getName() + " found");
542       }
543     } catch (final SQLException e) {
544       throw new DAOConnectionException(e);
545     } finally {
546       closeResultSet(res);
547       closeStatement(stm);
548     }
549   }
550 
551   @Override
552   public void insert(final E e1) throws DAOConnectionException {
553     if (isCachedEnable()) {
554       // Need to check since all does not accept getId
555       addToCache(getId(e1), e1);
556     }
557     final Object[] params;
558     try {
559       params = getInsertValues(e1);
560     } catch (final WaarpDatabaseSqlException e) {
561       throw new DAOConnectionException(e);
562     }
563 
564     PreparedStatement stm = null;
565     try {
566       stm = connection.prepareStatement(getInsertRequest());
567       setParameters(stm, params);
568       executeUpdate(stm);
569     } catch (final SQLException e) {
570       throw new DAOConnectionException(e);
571     } finally {
572       closeStatement(stm);
573     }
574   }
575 
576   @Override
577   public final void update(final E e1)
578       throws DAOConnectionException, DAONoDataException {
579     if (isCachedEnable()) {
580       // Need to check since all does not accept getId
581       addToCache(getId(e1), e1);
582     }
583     final Object[] params;
584     try {
585       params = getUpdateValues(e1);
586     } catch (final WaarpDatabaseSqlException e) {
587       throw new DAOConnectionException(e);
588     }
589 
590     PreparedStatement stm = null;
591     try {
592       stm = connection.prepareStatement(getUpdateRequest());
593       setParameters(stm, params);
594       try {
595         executeUpdate(stm);
596       } catch (final SQLException e2) {
597         throw new DAONoDataException(e2);
598       }
599     } catch (final SQLException e) {
600       throw new DAOConnectionException(e);
601     } finally {
602       closeStatement(stm);
603     }
604   }
605 
606 }