View Javadoc

1   /*
2    * #%L
3    * Bitrepository Integrity Service
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.integrityservice.workflow.step;
23  
24  import java.io.IOException;
25  import java.util.ArrayList;
26  import java.util.Collection;
27  import java.util.HashMap;
28  import java.util.HashSet;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Set;
32  
33  import org.bitrepository.bitrepositoryelements.FileAction;
34  import org.bitrepository.common.utils.SettingsUtils;
35  import org.bitrepository.integrityservice.cache.FileInfo;
36  import org.bitrepository.integrityservice.cache.IntegrityModel;
37  import org.bitrepository.integrityservice.cache.database.FileState;
38  import org.bitrepository.integrityservice.reports.IntegrityReporter;
39  import org.bitrepository.service.audit.AuditTrailManager;
40  import org.bitrepository.service.workflow.AbstractWorkFlowStep;
41  import org.slf4j.Logger;
42  import org.slf4j.LoggerFactory;
43  
44  /**
45   * A workflow step for finding inconsistency between the checksums.
46   */
47  public class HandleChecksumValidationStep extends AbstractWorkFlowStep {
48      /** The log.*/
49      private Logger log = LoggerFactory.getLogger(getClass());
50      /** The Integrity Model. */
51      private final IntegrityModel store;
52      /** The report model to populate */
53      private final IntegrityReporter reporter;
54      /** The audit trail manager.*/
55      private final AuditTrailManager auditManager;
56      
57      public HandleChecksumValidationStep(IntegrityModel store, AuditTrailManager auditManager, 
58              IntegrityReporter reporter) {
59          this.store = store;
60          this.auditManager = auditManager;
61          this.reporter = reporter;
62      }
63      
64      @Override
65      public String getName() {
66          return "Handle files missing at some pillars.";
67      }
68  
69      /**
70       * Queries the IntegrityModel for missing files on each pillar. Reports them if any is returned.
71       */
72      @Override
73      public synchronized void performStep() {
74          List<String> inconsistentFiles = store.getFilesWithInconsistentChecksums(reporter.getCollectionID());
75          List<String> collectionPillars = SettingsUtils.getPillarIDsForCollection(reporter.getCollectionID());
76          for(String file : inconsistentFiles) {
77              Collection<FileInfo> infos = store.getFileInfos(file, reporter.getCollectionID());
78              Set<String> checksums = getUniqueChecksums(infos);
79              if(checksums.size() > 1) {
80                  createAuditForInconsistentChecksum(infos, file);
81                  for(FileInfo info : infos) {
82                      try {
83                          reporter.reportChecksumIssue(file, info.getPillarId());
84                      } catch (IOException e) {
85                          log.error("Failed to report file: " + file + " as having a checksum issue", e);
86                      }
87                  }
88                  store.setChecksumError(file, getPillarsFileExisting(infos), reporter.getCollectionID());
89              } else {
90                  log.error("File with inconsistent checksums from SQL have apparently not inconsistency according to "
91                          + "Java! This is a scenario, which must never occur!!!");
92                  store.setChecksumAgreement(file, collectionPillars, reporter.getCollectionID());
93              }
94          }
95          
96          store.setFilesWithConsistentChecksumToValid(reporter.getCollectionID());
97      }
98      
99      /**
100      * Retrieves the unique checksums for the files, which exists.
101      * Ignores files with another filestate than 'EXISTING'.
102      * @param infos The FileInfo with information about the file at the different pillars. 
103      * @return The set of unique checksums.
104      */
105     private Set<String> getUniqueChecksums(Collection<FileInfo> infos) {
106         Set<String> checksums = new HashSet<String>();
107         
108         for(FileInfo info : infos) {
109             if((info.getChecksum() != null) && (info.getFileState() == FileState.EXISTING)) {
110                 checksums.add(info.getChecksum());
111             }
112         }
113         
114         return checksums;
115     }
116     
117     /**
118      * Extract the pillars where the file has state 'EXISTING'.
119      * @param infos The FileInfo with information about the file at the different pillars. 
120      * @return The list of pillars which has the file.
121      */
122     private List<String> getPillarsFileExisting(Collection<FileInfo> infos) {
123         List<String> res = new ArrayList<String>();
124         
125         for(FileInfo info : infos) {
126             if(info.getFileState() == FileState.EXISTING) {
127                 res.add(info.getPillarId());
128             }
129         }
130         
131         return res;
132     }
133     
134     /**
135      * Creates a audit-trail for inconsistency between checksums.
136      * If only one pillar is alone with a checksum compared to all the others, then it is pointed out at the possible 
137      * cause.
138      * @param infos The information about the file at all the pillars.
139      * @param fileId The id of the file.
140      */
141     private void createAuditForInconsistentChecksum(Collection<FileInfo> infos, String fileId) {
142         String auditText;
143         Map<String, List<String>> checksumMap = getChecksumMapping(infos);
144         String pillarId = findSingleInconsistentPillar(checksumMap);
145         
146         if(pillarId != null) {
147             auditText = "Checksum inconsistency for the file '" + fileId + "' at collection '" 
148                     + reporter.getCollectionID() + "'. Possibly corrupt at pillar '" + pillarId 
149                     + "', since all the other pillars agree upon another checksum.";
150         } else {
151             auditText = "Checksum inconsistency for the file '" + fileId + "' at collection '" 
152                     + reporter.getCollectionID() + "'. The pillars have registered more than one unique "
153                     + "checksum for the file.";
154         }
155         auditManager.addAuditEvent(reporter.getCollectionID(), fileId, "IntegrityService", 
156                 auditText, "IntegrityService validating the checksums.", FileAction.INCONSISTENCY);
157     }
158     
159     /**
160      * Retrieves the mapping between the checksums and the pillars, e.g. which pillars have a given checksum.
161      * @param infos The information about a given file at all the pillars. 
162      * @return The mapping between checksums and the pillars with that checksum.
163      */
164     private Map<String, List<String>> getChecksumMapping(Collection<FileInfo> infos) {
165         Map<String, List<String>> checksumMap = new HashMap<String, List<String>>();
166         for(FileInfo info : infos) {
167             List<String> pillarIdsForChecksum;
168             if(checksumMap.containsKey(info.getChecksum())) {
169                 pillarIdsForChecksum = checksumMap.get(info.getChecksum());
170             } else {
171                 pillarIdsForChecksum = new ArrayList<String>();
172             }
173             pillarIdsForChecksum.add(info.getPillarId());
174             checksumMap.put(info.getChecksum(), pillarIdsForChecksum);
175         }
176         
177         return checksumMap;
178     }
179     
180     /**
181      * Tries to find a single pillar, who causes the inconsistency, otherwise a null is returned.
182      * This pillar must be alone with its checksum, whereas all the other pillars (minimum 2 other pillars) must 
183      * agree upon another checksum. 
184      * @param checksumMap The mapping between checksums and the pillars with that checksum.
185      * @return The id of the pillar possibly causing the inconsistency, or null if no single pillar can be found.
186      */
187     private String findSingleInconsistentPillar(Map<String, List<String>> checksumMap) {
188         if(checksumMap.size() != 2) {
189             return null;
190         }
191         List<List<String>> pillarLists = new ArrayList<List<String>>(checksumMap.values());
192         
193         if((pillarLists.get(0).size() > 1) && (pillarLists.get(1).size() > 1)) {
194             return null;
195         }
196         if((pillarLists.get(0).size() == 1) && (pillarLists.get(1).size() == 1)) {
197             return null;
198         }
199         
200         if(pillarLists.get(0).size() == 1) {
201             return pillarLists.get(0).get(0);
202         } else {
203             return pillarLists.get(1).get(0);
204         }
205     }
206 }