View Javadoc

1   /*
2    * #%L
3    * Bitrepository Reference Pillar
4    * %%
5    * Copyright (C) 2010 - 2012 The State and University Library, The Royal Library and The State Archives, Denmark
6    * %%
7    * This program is free software: you can redistribute it and/or modify
8    * it under the terms of the GNU Lesser General Public License as 
9    * published by the Free Software Foundation, either version 2.1 of the 
10   * License, or (at your option) any later version.
11   * 
12   * This program is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   * GNU General Lesser Public License for more details.
16   * 
17   * You should have received a copy of the GNU General Lesser Public 
18   * License along with this program.  If not, see
19   * <http://www.gnu.org/licenses/lgpl-2.1.html>.
20   * #L%
21   */
22  package org.bitrepository.service.audit;
23  
24  import static org.bitrepository.service.audit.AuditDatabaseConstants.ACTOR_GUID;
25  import static org.bitrepository.service.audit.AuditDatabaseConstants.ACTOR_NAME;
26  import static org.bitrepository.service.audit.AuditDatabaseConstants.ACTOR_TABLE;
27  import static org.bitrepository.service.audit.AuditDatabaseConstants.AUDITTRAIL_ACTOR_GUID;
28  import static org.bitrepository.service.audit.AuditDatabaseConstants.AUDITTRAIL_AUDIT;
29  import static org.bitrepository.service.audit.AuditDatabaseConstants.AUDITTRAIL_FILE_GUID;
30  import static org.bitrepository.service.audit.AuditDatabaseConstants.AUDITTRAIL_INFORMATION;
31  import static org.bitrepository.service.audit.AuditDatabaseConstants.AUDITTRAIL_OPERATION;
32  import static org.bitrepository.service.audit.AuditDatabaseConstants.AUDITTRAIL_OPERATION_DATE;
33  import static org.bitrepository.service.audit.AuditDatabaseConstants.AUDITTRAIL_SEQUENCE_NUMBER;
34  import static org.bitrepository.service.audit.AuditDatabaseConstants.AUDITTRAIL_TABLE;
35  import static org.bitrepository.service.audit.AuditDatabaseConstants.FILE_COLLECTIONID;
36  import static org.bitrepository.service.audit.AuditDatabaseConstants.FILE_FILEID;
37  import static org.bitrepository.service.audit.AuditDatabaseConstants.FILE_GUID;
38  import static org.bitrepository.service.audit.AuditDatabaseConstants.FILE_TABLE;
39  
40  import java.math.BigInteger;
41  import java.sql.Connection;
42  import java.sql.PreparedStatement;
43  import java.sql.ResultSet;
44  import java.util.ArrayList;
45  import java.util.Date;
46  import java.util.List;
47  
48  import org.bitrepository.bitrepositoryelements.AuditTrailEvent;
49  import org.bitrepository.bitrepositoryelements.FileAction;
50  import org.bitrepository.common.ArgumentValidator;
51  import org.bitrepository.common.settings.Settings;
52  import org.bitrepository.common.utils.CalendarUtils;
53  import org.bitrepository.service.database.DBConnector;
54  import org.bitrepository.service.database.DatabaseManager;
55  import org.bitrepository.service.database.DatabaseUtils;
56  import org.slf4j.Logger;
57  import org.slf4j.LoggerFactory;
58  
59  /**
60   * Access interface for communication with the audit trail database.
61   *
62   * In the case of 'All-FileIDs', then the 'fileId' is given the string-value 'null'. 
63   */
64  public class AuditTrailContributerDAO implements AuditTrailManager {
65      /** The log.*/
66      private Logger log = LoggerFactory.getLogger(getClass());
67      /** The connection to the database.*/
68      private DBConnector dbConnector;
69      /** The componentID.*/
70      private final String componentID;
71  
72      /**
73       * Constructor.
74       * @param settings The settings.
75       */
76      public AuditTrailContributerDAO(Settings settings, DatabaseManager manager) {
77          ArgumentValidator.checkNotNull(settings, "settings");
78          
79          this.componentID = settings.getComponentID();
80          this.dbConnector = manager.getConnector();
81          
82          getConnection();
83      }
84  
85      /**
86       * Retrieve the connection to the database.
87       * TODO improve performance (only reconnect every-so-often)... 
88       * @return The connection to the database.
89       */
90      protected Connection getConnection() {
91          try {
92              Connection dbConnection = dbConnector.getConnection();
93              return dbConnection;
94          } catch (Exception e) {
95              throw new IllegalStateException("Could not instantiate the database", e);
96          }
97      }
98  
99      @Override
100     public void addAuditEvent(String collectionId, String fileId, String actor, String info, String auditTrail, FileAction operation) {
101         ArgumentValidator.checkNotNull(collectionId, "String collectionId");
102         ArgumentValidator.checkNotNull(operation, "FileAction operation");
103         log.trace("Inserting an audit event for file '" + fileId + "', from actor '" + actor
104                 + "' performing operation '" + operation + "', with the audit trail information '" + auditTrail + "'");
105 
106         long fileGuid;
107         if(fileId == null || fileId.isEmpty()) {
108             fileGuid = retrieveFileGuid(collectionId, "null");
109         } else {
110             fileGuid = retrieveFileGuid(collectionId, fileId);
111         }
112         long actorGuid;
113         if(actor == null || actor.isEmpty()) {
114             actorGuid = retrieveActorGuid("null");
115         } else {
116             actorGuid = retrieveActorGuid(actor);
117         }
118 
119         if(auditTrail == null) {
120             auditTrail = "";
121         }
122         if(info == null) {
123             info = "";
124         }
125 
126         synchronized(this) {
127             String insertSql = "INSERT INTO " + AUDITTRAIL_TABLE + " ( " + AUDITTRAIL_FILE_GUID + " , "
128                     + AUDITTRAIL_ACTOR_GUID + " , " + AUDITTRAIL_OPERATION + " , " + AUDITTRAIL_OPERATION_DATE + " , "
129                     + AUDITTRAIL_AUDIT + " , " + AUDITTRAIL_INFORMATION + " ) VALUES ( ? , ? , ? , ? , ? , ? )";
130             DatabaseUtils.executeStatement(dbConnector, insertSql, fileGuid, actorGuid, operation.toString(),
131                     new Date(), auditTrail, info);
132         }
133     }
134 
135     @Override
136     public AuditTrailDatabaseResults getAudits(String collectionId, String fileId, Long minSeqNumber, 
137             Long maxSeqNumber, Date minDate, Date maxDate, Long maxNumberOfResults) {
138         return extractEvents(new AuditTrailExtractor(collectionId, fileId, minSeqNumber, maxSeqNumber, minDate, 
139                 maxDate), maxNumberOfResults);
140     }
141 
142     /**
143      * Extracts the largest sequence number from the database. 
144      * @return The largest sequence number. If no entry exists, then zero is returned.
145      */
146     public Long extractLargestSequenceNumber() {
147         String sql = "SELECT " + AUDITTRAIL_SEQUENCE_NUMBER + " FROM " + AUDITTRAIL_TABLE + " ORDER BY "
148                 + AUDITTRAIL_SEQUENCE_NUMBER + " DESC";
149 
150         try {
151             PreparedStatement ps = null;
152             ResultSet res = null;
153             Connection conn = null;
154             try {
155                 conn = getConnection();
156                 ps = DatabaseUtils.createPreparedStatement(conn, sql, new Object[0]);
157                 res = ps.executeQuery();
158                 if(!res.next()) {
159                     return 0L;
160                 }
161                 return res.getLong(1);
162             } finally {
163                 if(res != null) {
164                     res.close();
165                 }
166                 if(ps != null) {
167                     ps.close();
168                 }
169                 if(conn != null) {
170                     conn.close();
171                 }
172             }
173         } catch (Exception e) {
174             throw new IllegalStateException("Could not use SQL query '" + sql
175                     + "' for retrieving the largest sequence number", e);
176 
177         }
178     }
179 
180     /**
181      * Extracts the the audit trail information based on the given sql query and arguments.
182      * @param extractor The entity for extracting the audit trails.
183      * @param maxNumberOfResults The maximum number of results.
184      * @return The extracted audit trails.
185      */
186     private AuditTrailDatabaseResults extractEvents(AuditTrailExtractor extractor, Long maxNumberOfResults) {
187 
188         final int sequencePosition = 1;
189         final int fileGuidPosition = 2;
190         final int actorPosition = 3;
191         final int actionDatePosition = 4;
192         final int operationPosition = 5;
193         final int auditTrailInformationPosition = 6;
194         final int infoPosition = 7;
195         
196         String sql = "SELECT " + AUDITTRAIL_SEQUENCE_NUMBER + ", " + AUDITTRAIL_FILE_GUID + " , "
197                 + AUDITTRAIL_ACTOR_GUID + " , " + AUDITTRAIL_OPERATION_DATE + " , " + AUDITTRAIL_OPERATION + " , "
198                 + AUDITTRAIL_AUDIT + " , " + AUDITTRAIL_INFORMATION + " FROM " + AUDITTRAIL_TABLE + " "
199                 + extractor.createRestriction();
200 
201         AuditTrailDatabaseResults res = new AuditTrailDatabaseResults();
202         try {
203             PreparedStatement ps = null;
204             ResultSet results = null;
205             Connection conn = null;
206             try {
207                 conn = getConnection();
208                 ps = DatabaseUtils.createPreparedStatement(conn, sql, extractor.getArguments());
209                 results = ps.executeQuery();
210                 
211                 int count = 0;
212                 while(results.next() && (maxNumberOfResults == null || count < maxNumberOfResults)) {
213                     count++;
214                     AuditTrailEvent event = new AuditTrailEvent();
215 
216                     event.setSequenceNumber(BigInteger.valueOf(results.getLong(sequencePosition)));
217                     event.setFileID(retrieveFileId(results.getLong(fileGuidPosition)));
218                     event.setActorOnFile(retrieveActorName(results.getLong(actorPosition)));
219                     event.setActionDateTime(CalendarUtils.getFromMillis(results.getTimestamp(actionDatePosition).getTime()));
220                     event.setActionOnFile(FileAction.fromValue(results.getString(operationPosition)));
221                     event.setAuditTrailInformation(results.getString(auditTrailInformationPosition));
222                     event.setInfo(results.getString(infoPosition));
223                     event.setReportingComponent(componentID);
224                     res.addAuditTrailEvent(event);
225                 }
226                 
227                 if(maxNumberOfResults != null && count >= maxNumberOfResults) {
228                     log.debug("More than the maximum {} results found.", maxNumberOfResults);
229                     res.reportMoreResultsFound();
230                 }
231             } finally {
232                 if(res != null) {
233                     results.close();
234                 }
235                 if(ps != null) {
236                     ps.close();
237                 }
238                 if(conn != null) {
239                     conn.close();
240                 }
241             }
242         } catch (Exception e) {
243             throw new IllegalStateException("Could not extract the audit trails events.", e);
244         }
245 
246         log.debug("Extracted audit trail events: {}", res);
247         return res;
248     }
249 
250     /**
251      * Retrieve the guid for a given file id. If the file id does not exist within the table, then it is created.
252      *
253      * @param collectionId The collection id for the file.
254      * @param fileId The id of the file to retrieve. 
255      * @return The guid for the given file id.
256      */
257     private synchronized long retrieveFileGuid(String collectionId, String fileId) {
258         String sqlRetrieve = "SELECT " + FILE_GUID + " FROM " + FILE_TABLE + " WHERE " + FILE_FILEID + " = ? AND " 
259                 + FILE_COLLECTIONID + " = ?";
260 
261         Long guid = DatabaseUtils.selectLongValue(dbConnector, sqlRetrieve, fileId, collectionId);
262 
263         if(guid == null) {
264             log.debug("Inserting fileid '" + fileId + "' into the file table.");
265             String sqlInsert = "INSERT INTO " + FILE_TABLE + " ( " + FILE_FILEID + " , " + FILE_COLLECTIONID 
266                     + " ) VALUES ( ? , ? )";
267             DatabaseUtils.executeStatement(dbConnector, sqlInsert, fileId, collectionId);
268 
269             guid = DatabaseUtils.selectLongValue(dbConnector, sqlRetrieve, fileId, collectionId);
270         }
271 
272         return guid;
273     }
274 
275     /**
276      * Retrieves a id of an file based on the guid.
277      * @param fileGuid The guid for the file table entry.
278      * @return The id of the file corresponding to the guid.
279      */
280     private String retrieveFileId(long fileGuid) {
281         String sqlRetrieve = "SELECT " + FILE_FILEID + " FROM " + FILE_TABLE + " WHERE " + FILE_GUID + " = ?";
282 
283         return DatabaseUtils.selectStringValue(dbConnector, sqlRetrieve, fileGuid);
284     }
285 
286     /**
287      * Retrieve the guid for a given actor. If the actor does not exist within the actor, then it is created.
288      *
289      * @param actorName The name of the actor.
290      * @return The guid of the actor with the given name.
291      */
292     private synchronized long retrieveActorGuid(String actorName) {
293         String sqlRetrieve = "SELECT " + ACTOR_GUID + " FROM " + ACTOR_TABLE + " WHERE " + ACTOR_NAME + " = ?";
294 
295         Long guid = DatabaseUtils.selectLongValue(dbConnector, sqlRetrieve, actorName);
296 
297         if(guid == null) {
298             log.debug("Inserting actor '" + actorName + "' into the actor table.");
299             String sqlInsert = "INSERT INTO " + ACTOR_TABLE + " ( " + ACTOR_NAME + " ) VALUES ( ? )";
300             DatabaseUtils.executeStatement(dbConnector, sqlInsert, actorName);
301 
302             guid = DatabaseUtils.selectLongValue(dbConnector, sqlRetrieve, actorName);
303         }
304 
305         return guid;
306     }
307 
308     /**
309      * Retrieves a name of an actor based on the guid. 
310      * @param actorGuid The guid of the actor.
311      * @return The name of the actor corresponding to guid.
312      */
313     private String retrieveActorName(long actorGuid) {
314         String sqlRetrieve = "SELECT " + ACTOR_NAME + " FROM " + ACTOR_TABLE + " WHERE " + ACTOR_GUID + " = ?";
315 
316         return DatabaseUtils.selectStringValue(dbConnector, sqlRetrieve, actorGuid);
317     }
318 
319     /**
320      * Class for encapsulating the request for extracting
321      */
322     private class AuditTrailExtractor {
323         /** The file id limitation for the request. */
324         private Long fileGuid;
325         /** The minimum sequence number limitation for the request.*/
326         private Long minSeqNumber;
327         /** The maximum sequence number limitation for the request.*/
328         private Long maxSeqNumber;
329         /** The minimum date limitation for the request.*/
330         private Date minDate;
331         /** The maxmimum date limitation for the request.*/
332         private Date maxDate;
333 
334         /**
335          * Constructor.
336          * @param collectionId The id of the collection.
337          * @param fileId The file id limitation for the request.
338          * @param minSeqNumber The minimum sequence number limitation for the request.
339          * @param maxSeqNumber The maximum sequence number limitation for the request.
340          * @param minDate The minimum date limitation for the request.
341          * @param maxDate The maximum date limitation for the request.
342          */
343         public AuditTrailExtractor(String collectionId, String fileId, Long minSeqNumber, Long maxSeqNumber, Date minDate,
344                                    Date maxDate) {
345             if(fileId == null) {
346                 this.fileGuid = null;
347             } else {
348                 this.fileGuid = retrieveFileGuid(collectionId, fileId);
349             }
350             this.minSeqNumber = minSeqNumber;
351             this.maxSeqNumber = maxSeqNumber;
352             this.minDate = minDate;
353             this.maxDate = maxDate;
354         }
355 
356         /**
357          * @return The restriction for the request.
358          */
359         public String createRestriction() {
360             // Handle the case with no restrictions.
361             if(fileGuid == null && minSeqNumber == null && maxSeqNumber == null && minDate == null && maxDate == null) {
362                 return "";
363             }
364 
365             StringBuilder res = new StringBuilder();
366 
367             if(fileGuid != null) {
368                 nextArgument(res);
369                 res.append(AUDITTRAIL_FILE_GUID + " = ?");
370             }
371 
372             if(minSeqNumber != null) {
373                 nextArgument(res);
374                 res.append(AUDITTRAIL_SEQUENCE_NUMBER + " >= ?");
375             }
376 
377             if(maxSeqNumber != null) {
378                 nextArgument(res);
379                 res.append(AUDITTRAIL_SEQUENCE_NUMBER + " <= ?");
380             }
381 
382             if(minDate != null) {
383                 nextArgument(res);
384                 res.append(AUDITTRAIL_OPERATION_DATE + " >= ?");
385             }
386 
387             if(maxDate != null) {
388                 nextArgument(res);
389                 res.append(AUDITTRAIL_OPERATION_DATE + " <= ?");
390             }
391 
392             return res.toString();
393         }
394 
395         /**
396          * Adds either ' AND ' or 'WHERE ' depending on whether it is the first restriction.
397          * @param res The StringBuilder where the restrictions are combined.
398          */
399         private void nextArgument(StringBuilder res) {
400             if(res.length() > 0) {
401                 res.append(" AND ");
402             } else {
403                 res.append("WHERE ");
404             }
405         }
406 
407         /**
408          * @return The arguments for the SQL statement.
409          */
410         public Object[] getArguments() {
411             List<Object> res = new ArrayList<Object>();
412             if(fileGuid != null) {
413                 res.add(fileGuid);
414             }
415             if(minSeqNumber != null) {
416                 res.add(minSeqNumber);
417             }
418             if(maxSeqNumber != null) {
419                 res.add(maxSeqNumber);
420             }
421             if(minDate != null) {
422                 res.add(minDate);
423             }
424             if(maxDate != null) {
425                 res.add(maxDate);
426             }
427 
428             return res.toArray();
429         }
430     }
431 }