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  package org.waarp.gateway.ftp.database.data;
21  
22  import org.dom4j.Document;
23  import org.waarp.common.command.ReplyCode;
24  import org.waarp.common.database.DbConstant;
25  import org.waarp.common.database.DbPreparedStatement;
26  import org.waarp.common.database.DbSession;
27  import org.waarp.common.database.data.AbstractDbData;
28  import org.waarp.common.database.data.DbValue;
29  import org.waarp.common.database.exception.WaarpDatabaseException;
30  import org.waarp.common.database.exception.WaarpDatabaseNoConnectionException;
31  import org.waarp.common.database.exception.WaarpDatabaseNoDataException;
32  import org.waarp.common.database.exception.WaarpDatabaseSqlException;
33  import org.waarp.common.exception.InvalidArgumentException;
34  import org.waarp.common.logging.WaarpLogger;
35  import org.waarp.common.logging.WaarpLoggerFactory;
36  import org.waarp.common.xml.XmlDecl;
37  import org.waarp.common.xml.XmlType;
38  import org.waarp.common.xml.XmlUtil;
39  import org.waarp.common.xml.XmlValue;
40  import org.waarp.ftp.core.command.FtpCommandCode;
41  import org.waarp.gateway.ftp.config.FileBasedConfiguration;
42  
43  import java.io.FileWriter;
44  import java.io.IOException;
45  import java.io.InvalidObjectException;
46  import java.io.Writer;
47  import java.sql.SQLException;
48  import java.sql.Timestamp;
49  import java.sql.Types;
50  import java.util.HashSet;
51  import java.util.Set;
52  
53  /**
54   * Transfer Log for FtpExec
55   */
56  public class DbTransferLog extends AbstractDbData {
57    private static final String ERROR_DURING_EXPORT_OR_PURGE =
58        "Error during export or purge";
59  
60    private static final String ERROR_DURING_PURGE = "Error during purge";
61  
62    private static final String NO_ROW_FOUND2 = "No row found";
63  
64    /**
65     * Internal Logger
66     */
67    private static final WaarpLogger logger =
68        WaarpLoggerFactory.getLogger(DbTransferLog.class);
69  
70    public enum Columns {
71      FILENAME, MODETRANS, STARTTRANS, STOPTRANS, TRANSINFO, INFOSTATUS,
72      UPDATEDINFO, USERID, ACCOUNTID, HOSTID, SPECIALID
73    }
74  
75    public static final int[] dbTypes = {
76        Types.VARCHAR, Types.NVARCHAR, Types.TIMESTAMP, Types.TIMESTAMP,
77        Types.VARCHAR, Types.INTEGER, Types.INTEGER, Types.NVARCHAR,
78        Types.NVARCHAR, Types.NVARCHAR, Types.BIGINT
79    };
80  
81    public static final String table = " TRANSFLOG ";
82  
83    public static final String fieldseq = "TRANSSEQ";
84  
85    public static final Columns[] indexes =
86        { Columns.STARTTRANS, Columns.UPDATEDINFO, Columns.INFOSTATUS };
87  
88    public static final String XMLRUNNERS = "transferlogs";
89    public static final String XMLRUNNER = "log";
90  
91    // Values
92    private String user;
93  
94    private String account;
95  
96    private long specialId;
97  
98    private boolean isSender;
99  
100   private String filename;
101 
102   private String mode;
103 
104   private Timestamp start;
105 
106   private Timestamp stop;
107 
108   private String infotransf;
109 
110   private String hostid;
111 
112   /**
113    * Info status error code
114    */
115   private ReplyCode infostatus = ReplyCode.REPLY_000_SPECIAL_NOSTATUS;
116 
117   /**
118    * The global status for running
119    */
120   private int updatedInfo = UpdatedInfo.UNKNOWN.ordinal();
121 
122   /**
123    * Special For DbTransferLog
124    */
125   public static final int NBPRKEY = 4;
126   // ALL TABLE SHOULD IMPLEMENT THIS
127 
128   protected static final String selectAllFields =
129       Columns.FILENAME.name() + ',' + Columns.MODETRANS.name() + ',' +
130       Columns.STARTTRANS.name() + ',' + Columns.STOPTRANS.name() + ',' +
131       Columns.TRANSINFO.name() + ',' + Columns.INFOSTATUS.name() + ',' +
132       Columns.UPDATEDINFO.name() + ',' + Columns.USERID.name() + ',' +
133       Columns.ACCOUNTID.name() + ',' + Columns.HOSTID.name() + ',' +
134       Columns.SPECIALID.name();
135 
136   protected static final String updateAllFields =
137       Columns.FILENAME.name() + "=?," + Columns.MODETRANS.name() + "=?," +
138       Columns.STARTTRANS.name() + "=?," + Columns.STOPTRANS.name() + "=?," +
139       Columns.TRANSINFO.name() + "=?," + Columns.INFOSTATUS.name() + "=?," +
140       Columns.UPDATEDINFO.name() + "=?";
141 
142   protected static final String insertAllValues = " (?,?,?,?,?,?,?,?,?,?,?) ";
143 
144   private static final Set<Long> clientNoDbSpecialId = new HashSet<Long>();
145 
146   /**
147    * Insert into database
148    *
149    * @param dbSession
150    * @param user
151    * @param account
152    * @param specialId
153    * @param isSender
154    * @param filename
155    * @param mode
156    * @param infostatus
157    * @param info
158    * @param updatedInfo
159    *
160    * @throws WaarpDatabaseException
161    */
162   public DbTransferLog(final DbSession dbSession, final String user,
163                        final String account, final long specialId,
164                        final boolean isSender, final String filename,
165                        final String mode, final ReplyCode infostatus,
166                        final String info, final UpdatedInfo updatedInfo)
167       throws WaarpDatabaseException {
168     super(dbSession);
169     this.user = user;
170     this.account = account;
171     this.specialId = specialId;
172     this.isSender = isSender;
173     this.filename = filename;
174     this.mode = mode;
175     start = new Timestamp(System.currentTimeMillis());
176     this.infostatus = infostatus;
177     infotransf = info;
178     this.updatedInfo = updatedInfo.ordinal();
179     hostid = FileBasedConfiguration.fileBasedConfiguration.getHostId();
180     setToArray();
181     isSaved = false;
182     insert();
183   }
184 
185   /**
186    * Load from database
187    *
188    * @param dbSession
189    * @param user
190    * @param account
191    * @param specialId
192    *
193    * @throws WaarpDatabaseException
194    */
195   public DbTransferLog(final DbSession dbSession, final String user,
196                        final String account, final long specialId)
197       throws WaarpDatabaseException {
198     super(dbSession);
199     this.user = user;
200     this.account = account;
201     this.specialId = specialId;
202     hostid = FileBasedConfiguration.fileBasedConfiguration.getHostId();
203     select();
204   }
205 
206   @Override
207   protected final void initObject() {
208     primaryKey = new DbValue[] {
209         new DbValue(user, Columns.USERID.name()),
210         new DbValue(account, Columns.ACCOUNTID.name()),
211         new DbValue(hostid, Columns.HOSTID.name()),
212         new DbValue(specialId, Columns.SPECIALID.name())
213     };
214     otherFields = new DbValue[] {
215         // FILENAME, MODETRANS,
216         // STARTTRANS, STOPTRANS, TRANSINFO
217         // INFOSTATUS, UPDATEDINFO
218         new DbValue(filename, Columns.FILENAME.name()),
219         new DbValue(mode, Columns.MODETRANS.name()),
220         new DbValue(start, Columns.STARTTRANS.name()),
221         new DbValue(stop, Columns.STOPTRANS.name()),
222         new DbValue(infotransf, Columns.TRANSINFO.name()),
223         new DbValue(ReplyCode.REPLY_000_SPECIAL_NOSTATUS.getCode(),
224                     Columns.INFOSTATUS.name()), // infostatus.getCode()
225         new DbValue(updatedInfo, Columns.UPDATEDINFO.name())
226     };
227     allFields = new DbValue[] {
228         otherFields[0], otherFields[1], otherFields[2], otherFields[3],
229         otherFields[4], otherFields[5], otherFields[6], primaryKey[0],
230         primaryKey[1], primaryKey[2], primaryKey[3]
231     };
232   }
233 
234   @Override
235   protected final String getSelectAllFields() {
236     return selectAllFields;
237   }
238 
239   @Override
240   protected final String getTable() {
241     return table;
242   }
243 
244   @Override
245   protected final String getInsertAllValues() {
246     return insertAllValues;
247   }
248 
249   @Override
250   protected final String getUpdateAllFields() {
251     return updateAllFields;
252   }
253 
254   @Override
255   protected final void setToArray() throws WaarpDatabaseSqlException {
256     // FILENAME, MODETRANS,
257     // STARTTRANS, STOPTRANS, TRANSINFO
258     // INFOSTATUS, UPDATEDINFO
259     // USERID, ACCOUNTID, SPECIALID
260     validateLength(Types.VARCHAR, filename, infotransf);
261     validateLength(Types.NVARCHAR, mode, user, account, hostid);
262     allFields[Columns.FILENAME.ordinal()].setValue(filename);
263     allFields[Columns.MODETRANS.ordinal()].setValue(mode);
264     allFields[Columns.STARTTRANS.ordinal()].setValue(start);
265     stop = new Timestamp(System.currentTimeMillis());
266     allFields[Columns.STOPTRANS.ordinal()].setValue(stop);
267     allFields[Columns.TRANSINFO.ordinal()].setValue(infotransf);
268     allFields[Columns.INFOSTATUS.ordinal()].setValue(infostatus.getCode());
269     allFields[Columns.UPDATEDINFO.ordinal()].setValue(updatedInfo);
270     allFields[Columns.USERID.ordinal()].setValue(user);
271     allFields[Columns.ACCOUNTID.ordinal()].setValue(account);
272     allFields[Columns.HOSTID.ordinal()].setValue(hostid);
273     allFields[Columns.SPECIALID.ordinal()].setValue(specialId);
274   }
275 
276   @Override
277   protected final void setFromArray() throws WaarpDatabaseSqlException {
278     filename = (String) allFields[Columns.FILENAME.ordinal()].getValue();
279     mode = (String) allFields[Columns.MODETRANS.ordinal()].getValue();
280     start = (Timestamp) allFields[Columns.STARTTRANS.ordinal()].getValue();
281     stop = (Timestamp) allFields[Columns.STOPTRANS.ordinal()].getValue();
282     try {
283       infostatus = ReplyCode.getReplyCode(
284           (Integer) allFields[Columns.INFOSTATUS.ordinal()].getValue());
285     } catch (final InvalidArgumentException e) {
286       throw new WaarpDatabaseSqlException("Wrong Argument", e);
287     }
288     infotransf = (String) allFields[Columns.TRANSINFO.ordinal()].getValue();
289     updatedInfo = (Integer) allFields[Columns.UPDATEDINFO.ordinal()].getValue();
290     user = (String) allFields[Columns.USERID.ordinal()].getValue();
291     account = (String) allFields[Columns.ACCOUNTID.ordinal()].getValue();
292     hostid = (String) allFields[Columns.HOSTID.ordinal()].getValue();
293     specialId = (Long) allFields[Columns.SPECIALID.ordinal()].getValue();
294   }
295 
296   /**
297    * @return The Where condition on Primary Key
298    */
299   @Override
300   protected final String getWherePrimaryKey() {
301     return primaryKey[0].getColumn() + " = ? AND " + primaryKey[1].getColumn() +
302            " = ? AND " + primaryKey[2].getColumn() + " = ? AND " +
303            primaryKey[3].getColumn() + " = ? ";
304   }
305 
306   /**
307    * Set the primary Key as current value
308    */
309   @Override
310   protected final void setPrimaryKey() {
311     primaryKey[0].setValue(user);
312     primaryKey[1].setValue(account);
313     primaryKey[2].setValue(hostid);
314     primaryKey[3].setValue(specialId);
315   }
316 
317   /**
318    * @return the condition to limit access to the row concerned by the Host
319    */
320   private static String getLimitWhereCondition() {
321     return " " + Columns.HOSTID + " = '" +
322            FileBasedConfiguration.fileBasedConfiguration.getHostId() + "' ";
323   }
324 
325   /**
326    * Create a Special Id for NoDb client
327    */
328   private void createNoDbSpecialId() {
329     synchronized (clientNoDbSpecialId) {
330       // New SpecialId is not possible with No Database Model
331       specialId = System.currentTimeMillis();
332       Long newOne = specialId;
333       while (clientNoDbSpecialId.contains(newOne)) {
334         newOne = specialId++;
335       }
336       clientNoDbSpecialId.add(newOne);
337     }
338   }
339 
340   /**
341    * Remove a Special Id for NoDb Client
342    */
343   private void removeNoDbSpecialId() {
344     synchronized (clientNoDbSpecialId) {
345       final Long oldOne = specialId;
346       clientNoDbSpecialId.remove(oldOne);
347     }
348   }
349 
350   @Override
351   public final void delete() throws WaarpDatabaseException {
352     if (dbSession == null) {
353       removeNoDbSpecialId();
354       return;
355     }
356     super.delete();
357   }
358 
359   @Override
360   public final void insert() throws WaarpDatabaseException {
361     if (isSaved) {
362       return;
363     }
364     if (dbSession == null) {
365       if (specialId == DbConstant.ILLEGALVALUE) {
366         // New SpecialId is not possible with No Database Model
367         createNoDbSpecialId();
368       }
369       isSaved = true;
370       return;
371     }
372     logger.debug("Dbrelated info: {}", dbSession.getAdmin().getServer());
373     // First need to find a new id if id is not ok
374     if (specialId == DbConstant.ILLEGALVALUE) {
375       specialId = dbSession.getAdmin().getDbModel().nextSequence(dbSession);
376       logger.debug("Try Insert create a new Id from sequence: {}", specialId);
377       setPrimaryKey();
378     }
379     super.insert();
380     logger.debug("TransferLog shall be created: {}", this);
381   }
382 
383   /**
384    * As insert but with the ability to change the SpecialId
385    *
386    * @throws WaarpDatabaseException
387    */
388   public final void create() throws WaarpDatabaseException {
389     if (isSaved) {
390       return;
391     }
392     if (dbSession == null) {
393       if (specialId == DbConstant.ILLEGALVALUE) {
394         // New SpecialId is not possible with No Database Model
395         createNoDbSpecialId();
396       }
397       isSaved = true;
398       return;
399     }
400     // First need to find a new id if id is not ok
401     if (specialId == DbConstant.ILLEGALVALUE) {
402       specialId = dbSession.getAdmin().getDbModel().nextSequence(dbSession);
403       logger.debug("Try Insert create a new Id from sequence: {}", specialId);
404       setPrimaryKey();
405     }
406     setToArray();
407     final DbPreparedStatement preparedStatement =
408         new DbPreparedStatement(dbSession);
409     try {
410       preparedStatement.createPrepareStatement(
411           "INSERT INTO " + table + " (" + selectAllFields + ") VALUES " +
412           insertAllValues);
413       setValues(preparedStatement, allFields);
414       try {
415         final int count = preparedStatement.executeUpdate();
416         if (count <= 0) {
417           throw new WaarpDatabaseNoDataException(NO_ROW_FOUND2);
418         }
419       } catch (final WaarpDatabaseSqlException e) {
420         logger.error("Problem while inserting: {}", e.getMessage());
421         final DbPreparedStatement find = new DbPreparedStatement(dbSession);
422         try {
423           find.createPrepareStatement(
424               "SELECT MAX(" + primaryKey[3].getColumn() + ") FROM " + table +
425               " WHERE " + primaryKey[0].getColumn() + " = ? AND " +
426               primaryKey[1].getColumn() + " = ? AND " +
427               primaryKey[2].getColumn() + " = ? AND " +
428               primaryKey[3].getColumn() + " != ? ");
429           setPrimaryKey();
430           setValues(find, primaryKey);
431           find.executeQuery();
432           if (find.getNext()) {
433             final long result;
434             try {
435               result = find.getResultSet().getLong(1);
436             } catch (final SQLException e1) {
437               throw new WaarpDatabaseSqlException(e1);
438             }
439             specialId = result + 1;
440             dbSession.getAdmin().getDbModel()
441                      .resetSequence(dbSession, specialId + 1);
442             setToArray();
443             preparedStatement.close();
444             setValues(preparedStatement, allFields);
445             final int count = preparedStatement.executeUpdate();
446             if (count <= 0) {
447               throw new WaarpDatabaseNoDataException(NO_ROW_FOUND2);
448             }
449           } else {
450             throw new WaarpDatabaseNoDataException(NO_ROW_FOUND2);
451           }
452         } finally {
453           find.realClose();
454         }
455       }
456       isSaved = true;
457     } finally {
458       preparedStatement.realClose();
459     }
460   }
461 
462   /**
463    * Private constructor
464    *
465    * @param dBsession
466    */
467   private DbTransferLog(final DbSession dBsession) {
468     super(dBsession);
469   }
470 
471   /**
472    * For instance when getting updated information
473    *
474    * @param preparedStatement
475    *
476    * @return the next updated DbTaskRunner
477    *
478    * @throws WaarpDatabaseNoConnectionException
479    * @throws WaarpDatabaseSqlException
480    */
481   public static DbTransferLog getFromStatement(
482       final DbPreparedStatement preparedStatement)
483       throws WaarpDatabaseNoConnectionException, WaarpDatabaseSqlException {
484     final DbTransferLog dbTaskRunner =
485         new DbTransferLog(preparedStatement.getDbSession());
486     dbTaskRunner.getValues(preparedStatement, dbTaskRunner.allFields);
487     dbTaskRunner.setFromArray();
488     dbTaskRunner.isSaved = true;
489     return dbTaskRunner;
490   }
491 
492   /**
493    * @param session
494    * @param status
495    * @param limit limit the number of rows
496    *
497    * @return the DbPreparedStatement for getting TransferLog according to
498    *     status
499    *     ordered by start
500    *
501    * @throws WaarpDatabaseNoConnectionException
502    * @throws WaarpDatabaseSqlException
503    */
504   public static DbPreparedStatement getStatusPrepareStament(
505       final DbSession session, final ReplyCode status, final int limit)
506       throws WaarpDatabaseNoConnectionException, WaarpDatabaseSqlException {
507     String request = "SELECT " + selectAllFields + " FROM " + table;
508     if (status != null) {
509       request +=
510           " WHERE " + Columns.INFOSTATUS.name() + " = " + status.getCode() +
511           " AND " + getLimitWhereCondition();
512     } else {
513       request += " WHERE " + getLimitWhereCondition();
514     }
515     request += " ORDER BY " + Columns.STARTTRANS.name() + " DESC ";
516     if (limit > 0) {
517       request = session.getAdmin().getDbModel()
518                        .limitRequest(selectAllFields, request, limit);
519     }
520     return new DbPreparedStatement(session, request);
521   }
522 
523   /**
524    * @param session
525    * @param start
526    * @param stop
527    *
528    * @return the DbPreparedStatement for getting Selected Object, whatever
529    *     their
530    *     status
531    *
532    * @throws WaarpDatabaseNoConnectionException
533    * @throws WaarpDatabaseSqlException
534    */
535   public static DbPreparedStatement getLogPrepareStament(
536       final DbSession session, final Timestamp start, final Timestamp stop)
537       throws WaarpDatabaseNoConnectionException, WaarpDatabaseSqlException {
538     return getLogPrepareStament(session, start, stop, null);
539   }
540 
541   /**
542    * @param session
543    * @param start
544    * @param stop
545    *
546    * @return the DbPreparedStatement for getting Selected Object, whatever
547    *     their
548    *     status
549    *
550    * @throws WaarpDatabaseNoConnectionException
551    * @throws WaarpDatabaseSqlException
552    */
553   public static DbPreparedStatement getLogPrepareStament(
554       final DbSession session, final Timestamp start, final Timestamp stop,
555       final ReplyCode status)
556       throws WaarpDatabaseNoConnectionException, WaarpDatabaseSqlException {
557 
558     String statusWhereFilter = "";
559     if (status != null) {
560       statusWhereFilter =
561           Columns.INFOSTATUS.name() + " = " + status.getCode() + " AND ";
562     }
563 
564     final DbPreparedStatement preparedStatement =
565         new DbPreparedStatement(session);
566     String request = "SELECT " + selectAllFields + " FROM " + table;
567     if (start != null && stop != null) {
568       request += " WHERE " + statusWhereFilter + Columns.STARTTRANS.name() +
569                  " >= ? AND " + Columns.STARTTRANS.name() + " <= ? AND " +
570                  getLimitWhereCondition() + " ORDER BY " +
571                  Columns.SPECIALID.name() + " DESC ";
572       preparedStatement.createPrepareStatement(request);
573       try {
574         preparedStatement.getPreparedStatement().setTimestamp(1, start);
575         preparedStatement.getPreparedStatement().setTimestamp(2, stop);
576       } catch (final SQLException e) {
577         preparedStatement.realClose();
578         throw new WaarpDatabaseSqlException(e);
579       }
580     } else if (start != null) {
581       request += " WHERE " + statusWhereFilter + Columns.STARTTRANS.name() +
582                  " >= ? AND " + getLimitWhereCondition() + " ORDER BY " +
583                  Columns.SPECIALID.name() + " DESC ";
584       preparedStatement.createPrepareStatement(request);
585       try {
586         preparedStatement.getPreparedStatement().setTimestamp(1, start);
587       } catch (final SQLException e) {
588         preparedStatement.realClose();
589         throw new WaarpDatabaseSqlException(e);
590       }
591     } else if (stop != null) {
592       request += " WHERE " + statusWhereFilter + Columns.STARTTRANS.name() +
593                  " <= ? AND " + getLimitWhereCondition() + " ORDER BY " +
594                  Columns.SPECIALID.name() + " DESC ";
595       preparedStatement.createPrepareStatement(request);
596       try {
597         preparedStatement.getPreparedStatement().setTimestamp(1, stop);
598       } catch (final SQLException e) {
599         preparedStatement.realClose();
600         throw new WaarpDatabaseSqlException(e);
601       }
602     } else {
603       request += " WHERE " + statusWhereFilter + getLimitWhereCondition() +
604                  " ORDER BY " + Columns.SPECIALID.name() + " DESC ";
605       preparedStatement.createPrepareStatement(request);
606     }
607     return preparedStatement;
608   }
609 
610   /**
611    * @param session
612    *
613    * @return the DbPreparedStatement for getting Updated Object
614    *
615    * @throws WaarpDatabaseNoConnectionException
616    * @throws WaarpDatabaseSqlException
617    */
618   public static DbPreparedStatement getCountInfoPrepareStatement(
619       final DbSession session)
620       throws WaarpDatabaseNoConnectionException, WaarpDatabaseSqlException {
621     final String request =
622         "SELECT COUNT(" + Columns.SPECIALID.name() + ") FROM " + table +
623         " WHERE " + Columns.STARTTRANS.name() + " >= ? AND " +
624         getLimitWhereCondition() + " AND " + Columns.UPDATEDINFO.name() +
625         " = ? ";
626     final DbPreparedStatement pstt = new DbPreparedStatement(session, request);
627     session.addLongTermPreparedStatement(pstt);
628     return pstt;
629   }
630 
631   /**
632    * @param pstt
633    * @param info
634    * @param time
635    *
636    * @return the number of elements (COUNT) from the statement
637    */
638   public static long getResultCountPrepareStatement(
639       final DbPreparedStatement pstt, final UpdatedInfo info, final long time) {
640     long result = 0;
641     try {
642       finishSelectOrCountPrepareStatement(pstt, time);
643       pstt.getPreparedStatement().setInt(2, info.ordinal());
644       pstt.executeQuery();
645       if (pstt.getNext()) {
646         result = pstt.getResultSet().getLong(1);
647       }
648     } catch (final WaarpDatabaseNoConnectionException ignored) {
649       // nothing
650     } catch (final WaarpDatabaseSqlException ignored) {
651       // nothing
652     } catch (final SQLException ignored) {
653       // nothing
654     } finally {
655       pstt.close();
656     }
657     return result;
658   }
659 
660   /**
661    * @param session
662    *
663    * @return the DbPreparedStatement for getting Runner according to status
664    *     ordered by start
665    *
666    * @throws WaarpDatabaseNoConnectionException
667    * @throws WaarpDatabaseSqlException
668    */
669   public static DbPreparedStatement getCountStatusPrepareStatement(
670       final DbSession session)
671       throws WaarpDatabaseNoConnectionException, WaarpDatabaseSqlException {
672     String request =
673         "SELECT COUNT(" + Columns.SPECIALID.name() + ") FROM " + table;
674     request += " WHERE " + Columns.STARTTRANS.name() + " >= ? ";
675     request += " AND " + Columns.INFOSTATUS.name() + " = ? AND " +
676                getLimitWhereCondition();
677     final DbPreparedStatement prep = new DbPreparedStatement(session, request);
678     session.addLongTermPreparedStatement(prep);
679     return prep;
680   }
681 
682   /**
683    * @param session
684    *
685    * @return the DbPreparedStatement for getting All according to status
686    *     ordered
687    *     by start
688    *
689    * @throws WaarpDatabaseNoConnectionException
690    * @throws WaarpDatabaseSqlException
691    */
692   public static DbPreparedStatement getCountAllPrepareStatement(
693       final DbSession session)
694       throws WaarpDatabaseNoConnectionException, WaarpDatabaseSqlException {
695     String request =
696         "SELECT COUNT(" + Columns.SPECIALID.name() + ") FROM " + table;
697     request += " WHERE " + Columns.STARTTRANS.name() + " >= ? ";
698     request += " AND " + getLimitWhereCondition();
699     final DbPreparedStatement prep = new DbPreparedStatement(session, request);
700     session.addLongTermPreparedStatement(prep);
701     return prep;
702   }
703 
704   /**
705    * @param pstt
706    * @param error
707    * @param time
708    *
709    * @return the number of elements (COUNT) from the statement
710    */
711   public static long getResultCountPrepareStatement(
712       final DbPreparedStatement pstt, final ReplyCode error, final long time) {
713     long result = 0;
714     try {
715       finishSelectOrCountPrepareStatement(pstt, time);
716       pstt.getPreparedStatement().setInt(2, error.getCode());
717       pstt.executeQuery();
718       if (pstt.getNext()) {
719         result = pstt.getResultSet().getLong(1);
720       }
721     } catch (final WaarpDatabaseNoConnectionException ignored) {
722       // nothing
723     } catch (final WaarpDatabaseSqlException ignored) {
724       // nothing
725     } catch (final SQLException ignored) {
726       // nothing
727     } finally {
728       pstt.close();
729     }
730     return result;
731   }
732 
733   /**
734    * @param pstt
735    *
736    * @return the number of elements (COUNT) from the statement
737    */
738   public static long getResultCountPrepareStatement(
739       final DbPreparedStatement pstt) {
740     long result = 0;
741     try {
742       pstt.executeQuery();
743       if (pstt.getNext()) {
744         result = pstt.getResultSet().getLong(1);
745       }
746     } catch (final WaarpDatabaseNoConnectionException ignored) {
747       // nothing
748     } catch (final WaarpDatabaseSqlException ignored) {
749       // nothing
750     } catch (final SQLException ignored) {
751       // nothing
752     } finally {
753       pstt.close();
754     }
755     return result;
756   }
757 
758   /**
759    * Set the current time in the given updatedPreparedStatement
760    *
761    * @param pstt
762    *
763    * @throws WaarpDatabaseNoConnectionException
764    * @throws WaarpDatabaseSqlException
765    */
766   public static void finishSelectOrCountPrepareStatement(
767       final DbPreparedStatement pstt)
768       throws WaarpDatabaseNoConnectionException, WaarpDatabaseSqlException {
769     finishSelectOrCountPrepareStatement(pstt, System.currentTimeMillis());
770   }
771 
772   /**
773    * Set the current time in the given updatedPreparedStatement
774    *
775    * @param pstt
776    *
777    * @throws WaarpDatabaseNoConnectionException
778    * @throws WaarpDatabaseSqlException
779    */
780   public static void finishSelectOrCountPrepareStatement(
781       final DbPreparedStatement pstt, final long time)
782       throws WaarpDatabaseNoConnectionException, WaarpDatabaseSqlException {
783     final Timestamp startlimit = new Timestamp(time);
784     try {
785       pstt.getPreparedStatement().setTimestamp(1, startlimit);
786     } catch (final SQLException e) {
787       logger.error("Database SQL Error: Cannot set timestamp {}",
788                    e.getMessage());
789       throw new WaarpDatabaseSqlException("Cannot set timestamp", e);
790     }
791   }
792 
793   /**
794    * Running or not transfers are concerned
795    *
796    * @param session
797    * @param in True for Incoming, False for Outgoing
798    *
799    * @return the DbPreparedStatement for getting Runner according to in or out
800    *     going way and Error
801    *
802    * @throws WaarpDatabaseNoConnectionException
803    * @throws WaarpDatabaseSqlException
804    */
805   public static DbPreparedStatement getCountInOutErrorPrepareStatement(
806       final DbSession session, final boolean in)
807       throws WaarpDatabaseNoConnectionException, WaarpDatabaseSqlException {
808     String request =
809         "SELECT COUNT(" + Columns.SPECIALID.name() + ") FROM " + table;
810     final String inCond;
811     if (in) {
812       inCond = " (" + Columns.MODETRANS.name() + " = '" +
813                FtpCommandCode.APPE.name() + "' OR " + Columns.MODETRANS.name() +
814                " = '" + FtpCommandCode.STOR.name() + "' OR " +
815                Columns.MODETRANS.name() + " = '" + FtpCommandCode.STOU.name() +
816                "') ";
817     } else {
818       inCond = " (" + Columns.MODETRANS.name() + " = '" +
819                FtpCommandCode.RETR.name() + "') ";
820     }
821     request += " WHERE " + inCond;
822     request += " AND " + getLimitWhereCondition() + ' ';
823     request += " AND " + Columns.STARTTRANS.name() + " >= ? ";
824     request += " AND " + Columns.UPDATEDINFO.name() + " = " +
825                UpdatedInfo.INERROR.ordinal();
826     final DbPreparedStatement prep = new DbPreparedStatement(session, request);
827     session.addLongTermPreparedStatement(prep);
828     return prep;
829   }
830 
831   /**
832    * Running or not transfers are concerned
833    *
834    * @param session
835    * @param in True for Incoming, False for Outgoing
836    * @param running True for Running only, False for all
837    *
838    * @return the DbPreparedStatement for getting Runner according to in or out
839    *     going way
840    *
841    * @throws WaarpDatabaseNoConnectionException
842    * @throws WaarpDatabaseSqlException
843    */
844   public static DbPreparedStatement getCountInOutRunningPrepareStatement(
845       final DbSession session, final boolean in, final boolean running)
846       throws WaarpDatabaseNoConnectionException, WaarpDatabaseSqlException {
847     String request =
848         "SELECT COUNT(" + Columns.SPECIALID.name() + ") FROM " + table;
849     final String inCond;
850     if (in) {
851       inCond = " (" + Columns.MODETRANS.name() + " = '" +
852                FtpCommandCode.APPE.name() + "' OR " + Columns.MODETRANS.name() +
853                " = '" + FtpCommandCode.STOR.name() + "' OR " +
854                Columns.MODETRANS.name() + " = '" + FtpCommandCode.STOU.name() +
855                "') ";
856     } else {
857       inCond = " (" + Columns.MODETRANS.name() + " = '" +
858                FtpCommandCode.RETR.name() + "') ";
859     }
860     request += " WHERE " + inCond;
861     request += " AND " + getLimitWhereCondition() + ' ';
862     request += " AND " + Columns.STARTTRANS.name() + " >= ? ";
863     if (running) {
864       request += " AND " + Columns.UPDATEDINFO.name() + " = " +
865                  UpdatedInfo.RUNNING.ordinal();
866     }
867     final DbPreparedStatement prep = new DbPreparedStatement(session, request);
868     session.addLongTermPreparedStatement(prep);
869     return prep;
870   }
871 
872   @Override
873   public final void changeUpdatedInfo(final UpdatedInfo info) {
874     updatedInfo = info.ordinal();
875     allFields[Columns.UPDATEDINFO.ordinal()].setValue(updatedInfo);
876     isSaved = false;
877   }
878 
879   /**
880    * Set the ReplyCode for the UpdatedInfo
881    *
882    * @param code
883    */
884   public final void setReplyCodeExecutionStatus(final ReplyCode code) {
885     if (infostatus != code) {
886       infostatus = code;
887       allFields[Columns.INFOSTATUS.ordinal()].setValue(infostatus.getCode());
888       isSaved = false;
889     }
890   }
891 
892   /**
893    * @return The current UpdatedInfo value
894    */
895   public final UpdatedInfo getUpdatedInfo() {
896     return UpdatedInfo.values()[updatedInfo];
897   }
898 
899   /**
900    * @return the ReplyCode code associated with the Updated Info
901    */
902   public final ReplyCode getErrorInfo() {
903     return infostatus;
904   }
905 
906   /**
907    * @param filename the filename to set
908    */
909   public final void setFilename(final String filename) {
910     if (!this.filename.equals(filename)) {
911       this.filename = filename;
912       allFields[Columns.FILENAME.ordinal()].setValue(this.filename);
913       isSaved = false;
914     }
915   }
916 
917   /**
918    * @return the isSender
919    */
920   public final boolean isSender() {
921     return isSender;
922   }
923 
924   /**
925    * @return the filename
926    */
927   public final String getFilename() {
928     return filename;
929   }
930 
931   /**
932    * @return the specialId
933    */
934   public final long getSpecialId() {
935     return specialId;
936   }
937 
938   /**
939    * @return the infotransf
940    */
941   public final String getInfotransf() {
942     return infotransf;
943   }
944 
945   /**
946    * @param infotransf the infotransf to set
947    */
948   public final void setInfotransf(final String infotransf) {
949     this.infotransf = infotransf;
950   }
951 
952   /**
953    * @return the user
954    */
955   public final String getUser() {
956     return user;
957   }
958 
959   /**
960    * @return the account
961    */
962   public final String getAccount() {
963     return account;
964   }
965 
966   /**
967    * @param stop the stop to set
968    */
969   public final void setStop(final Timestamp stop) {
970     this.stop = stop;
971   }
972 
973   /**
974    * @return the mode
975    */
976   public final String getMode() {
977     return mode;
978   }
979 
980   /**
981    * This method is to be called each time an operation is happening on Runner
982    *
983    * @throws WaarpDatabaseException
984    */
985   public final void saveStatus() throws WaarpDatabaseException {
986     update();
987   }
988 
989   /**
990    * Clear the runner
991    */
992   public final void clear() {
993     // nothing
994   }
995 
996   @Override
997   public final String toString() {
998     return "Transfer: on " + filename + " SpecialId: " + specialId + " Mode: " +
999            mode + " isSender: " + isSender + " User: " + user + " Account: " +
1000            account + " Start: " + start + " Stop: " + stop + " Internal: " +
1001            UpdatedInfo.values()[updatedInfo].name() + ':' +
1002            infostatus.getMesg() + " TransferInfo: " + infotransf;
1003   }
1004 
1005   /**
1006    * @return the start
1007    */
1008   public final Timestamp getStart() {
1009     return start;
1010   }
1011 
1012   /**
1013    * @return the stop
1014    */
1015   public final Timestamp getStop() {
1016     return stop;
1017   }
1018 
1019   /*
1020    * XXXIDXXX XXXUSERXXX XXXACCTXXX XXXFILEXXX XXXMODEXXX XXXSTATUSXXX XXXINFOXXX XXXUPINFXXX XXXSTARTXXX
1021    * XXXSTOPXXX
1022    */
1023   private static final String XML_IDX = "IDX";
1024   private static final String XML_USER = "USER";
1025   private static final String XML_ACCT = "ACCT";
1026   private static final String XML_FILE = "FILE";
1027   private static final String XML_MODE = "MODE";
1028   private static final String XML_STATUS = "STATUS";
1029   private static final String XML_INFO = "INFO";
1030   private static final String XML_UPDINFO = "UPDINFO";
1031   private static final String XML_START = "START";
1032   private static final String XML_STOP = "STOP";
1033   private static final String XML_ROOT = "LOGS";
1034   private static final String XML_ENTRY = "LOG";
1035   /**
1036    * Structure of the Configuration file
1037    */
1038   private static final XmlDecl[] logDecls = {
1039       // identity
1040       new XmlDecl(XmlType.STRING, XML_IDX),
1041       new XmlDecl(XmlType.STRING, XML_USER),
1042       new XmlDecl(XmlType.STRING, XML_ACCT),
1043       new XmlDecl(XmlType.STRING, XML_FILE),
1044       new XmlDecl(XmlType.STRING, XML_MODE),
1045       new XmlDecl(XmlType.STRING, XML_STATUS),
1046       new XmlDecl(XmlType.STRING, XML_INFO),
1047       new XmlDecl(XmlType.STRING, XML_UPDINFO),
1048       new XmlDecl(XmlType.STRING, XML_START),
1049       new XmlDecl(XmlType.STRING, XML_STOP),
1050   };
1051   /**
1052    * Global Structure for Server Configuration
1053    */
1054   private static final XmlDecl[] logsElements = {
1055       new XmlDecl(XML_ENTRY, XmlType.XVAL, XML_ROOT + '/' + XML_ENTRY, logDecls,
1056                   true)
1057   };
1058 
1059   /**
1060    * @return the associated XmlValue
1061    */
1062   private XmlValue[] saveIntoXmlValue() {
1063     final XmlValue[] values = new XmlValue[logDecls.length];
1064     for (int i = 0; i < logDecls.length; i++) {
1065       values[i] = new XmlValue(logDecls[i]);
1066     }
1067     try {
1068       values[0].setFromString(Long.toString(specialId));
1069       values[1].setFromString(user);
1070       values[2].setFromString(account);
1071       values[3].setFromString(filename);
1072       values[4].setFromString(mode);
1073       values[5].setFromString(getErrorInfo().getMesg());
1074       values[6].setFromString(infotransf);
1075       values[7].setFromString(getUpdatedInfo().name());
1076       values[8].setFromString(start.toString());
1077       values[9].setFromString(stop.toString());
1078     } catch (final InvalidArgumentException e) {
1079       return null;
1080     }
1081     return values;
1082   }
1083 
1084   /**
1085    * Save the current DbTransferLog to a file
1086    *
1087    * @param filename
1088    *
1089    * @return The message for the HTTPS interface
1090    */
1091   public final String saveDbTransferLog(final String filename) {
1092     final Document document = XmlUtil.createEmptyDocument();
1093     final XmlValue[] roots = new XmlValue[1];
1094     final XmlValue root = new XmlValue(logsElements[0]);
1095     roots[0] = root;
1096     String message;
1097     final XmlValue[] values = saveIntoXmlValue();
1098     if (values == null) {
1099       return "Error during export";
1100     }
1101     try {
1102       root.addValue(values);
1103     } catch (final InvalidObjectException e) {
1104       logger.error("Error during Write DbTransferLog file {}", e.getMessage());
1105       return ERROR_DURING_PURGE;
1106     }
1107     try {
1108       delete();
1109       message = "Purge Correct Logs successful";
1110     } catch (final WaarpDatabaseException e) {
1111       message = ERROR_DURING_PURGE;
1112     }
1113     XmlUtil.write(document, roots);
1114     try {
1115       XmlUtil.saveDocument(filename, document);
1116     } catch (final IOException e1) {
1117       logger.error("Cannot write to file: " + filename + " since {}",
1118                    e1.getMessage());
1119       return message + " but cannot save file as export";
1120     }
1121     return message;
1122   }
1123 
1124   /**
1125    * Exports DbTransferLogs to a file and purges the corresponding
1126    * DbTransferLogs
1127    *
1128    * @param preparedStatement the DbTransferLog as SELECT command to
1129    *     export
1130    *     (and purge)
1131    * @param filename the filename where the DbLogs will be exported
1132    *
1133    * @return The message for the HTTPS interface
1134    */
1135   public static String saveDbTransferLogFile(
1136       final DbPreparedStatement preparedStatement, final String filename) {
1137     final Writer outWriter;
1138     try {
1139       outWriter = new FileWriter(filename);
1140     } catch (final IOException e) {
1141       return "Cannot open file " + filename + ": " + e.getMessage();
1142     }
1143 
1144     return saveDbTransferLogFile(preparedStatement, outWriter, true);
1145   }
1146 
1147   /**
1148    * Exports DbTransferLogs to a Writer object and optionally purges the
1149    * corresponding DbTransferLogs
1150    *
1151    * @param preparedStatement the DbTransferLog as SELECT command to
1152    *     export
1153    *     (and purge)
1154    * @param outWriter a Writer object where the DbLogs will be written
1155    * @param purge sets whether or not the selected results must be
1156    *     purged
1157    *
1158    * @return The message for the HTTPS interface
1159    */
1160   public static String saveDbTransferLogFile(
1161       final DbPreparedStatement preparedStatement, final Writer outWriter,
1162       final boolean purge) {
1163     final Document document = XmlUtil.createEmptyDocument();
1164     final XmlValue[] roots = new XmlValue[1];
1165     final XmlValue root = new XmlValue(logsElements[0]);
1166     roots[0] = root;
1167     String message = null;
1168     try {
1169       try {
1170         preparedStatement.executeQuery();
1171         while (preparedStatement.getNext()) {
1172           final DbTransferLog log = getFromStatement(preparedStatement);
1173           final XmlValue[] values = log.saveIntoXmlValue();
1174           if (values == null) {
1175             return "Error during export";
1176           }
1177           try {
1178             root.addValue(values);
1179           } catch (final InvalidObjectException e) {
1180             logger.error("Error during Write DbTransferLog file {}",
1181                          e.getMessage());
1182             return ERROR_DURING_PURGE;
1183           }
1184 
1185           if (purge) {
1186             log.delete();
1187           }
1188         }
1189       } catch (final WaarpDatabaseNoConnectionException e) {
1190         message = ERROR_DURING_EXPORT_OR_PURGE;
1191       } catch (final WaarpDatabaseSqlException e) {
1192         message = ERROR_DURING_EXPORT_OR_PURGE;
1193       } catch (final WaarpDatabaseException e) {
1194         message = ERROR_DURING_EXPORT_OR_PURGE;
1195       }
1196     } finally {
1197       preparedStatement.realClose();
1198     }
1199 
1200     XmlUtil.write(document, roots);
1201     try {
1202       XmlUtil.saveDocument(outWriter, document);
1203       message = "Logs exported " + (purge? "and purged" : "") + " successfully";
1204     } catch (final IOException e1) {
1205       logger.error("Cannot write to file since {}", e1.getMessage());
1206       return message + " but cannot save file as export";
1207     }
1208     return message;
1209   }
1210 }