View Javadoc

1   /*
2    * #%L
3    * Bitrepository Integrity Client
4    * 
5    * $Id$
6    * $HeadURL$
7    * %%
8    * Copyright (C) 2010 - 2012 The State and University Library, The Royal Library and The State Archives, Denmark
9    * %%
10   * This program is free software: you can redistribute it and/or modify
11   * it under the terms of the GNU Lesser General Public License as 
12   * published by the Free Software Foundation, either version 2.1 of the 
13   * License, or (at your option) any later version.
14   * 
15   * This program is distributed in the hope that it will be useful,
16   * but WITHOUT ANY WARRANTY; without even the implied warranty of
17   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18   * GNU General Lesser Public License for more details.
19   * 
20   * You should have received a copy of the GNU General Lesser Public 
21   * License along with this program.  If not, see
22   * <http://www.gnu.org/licenses/lgpl-2.1.html>.
23   * #L%
24   */
25  package org.bitrepository.integrityservice.web;
26  
27  import java.io.File;
28  import java.io.FileInputStream;
29  import java.io.FileNotFoundException;
30  import java.io.IOException;
31  import java.io.OutputStream;
32  import java.util.Date;
33  import java.util.HashMap;
34  import java.util.LinkedList;
35  import java.util.List;
36  import java.util.Map;
37  
38  import javax.ws.rs.Consumes;
39  import javax.ws.rs.DefaultValue;
40  import javax.ws.rs.FormParam;
41  import javax.ws.rs.GET;
42  import javax.ws.rs.POST;
43  import javax.ws.rs.Path;
44  import javax.ws.rs.Produces;
45  import javax.ws.rs.QueryParam;
46  import javax.ws.rs.WebApplicationException;
47  import javax.ws.rs.core.MediaType;
48  import javax.ws.rs.core.Response;
49  import javax.ws.rs.core.StreamingOutput;
50  
51  import org.bitrepository.common.utils.FileSizeUtils;
52  import org.bitrepository.common.utils.SettingsUtils;
53  import org.bitrepository.common.utils.TimeUtils;
54  import org.bitrepository.integrityservice.IntegrityServiceManager;
55  import org.bitrepository.integrityservice.cache.CollectionStat;
56  import org.bitrepository.integrityservice.cache.IntegrityModel;
57  import org.bitrepository.integrityservice.cache.PillarStat;
58  import org.bitrepository.integrityservice.cache.database.IntegrityIssueIterator;
59  import org.bitrepository.integrityservice.workflow.IntegrityCheckWorkflow;
60  import org.bitrepository.service.workflow.JobID;
61  import org.bitrepository.service.workflow.Workflow;
62  import org.bitrepository.service.workflow.WorkflowManager;
63  import org.bitrepository.service.workflow.WorkflowStatistic;
64  import org.json.JSONArray;
65  import org.json.JSONException;
66  import org.json.JSONObject;
67  import org.slf4j.Logger;
68  import org.slf4j.LoggerFactory;
69  
70  @Path("/IntegrityService")
71  public class RestIntegrityService {
72      private final Logger log = LoggerFactory.getLogger(getClass());
73      private IntegrityModel model;
74      private WorkflowManager workflowManager;
75      
76      private final static String JSON_LIST_START = "[";
77      private final static String JSON_LIST_END = "]";
78      private final static String JSON_LIST_SEPERATOR = ",";
79      private final static String JSON_DELIMITER = "\"";
80  
81      public RestIntegrityService() {
82          this.model = IntegrityServiceManager.getIntegrityModel();
83          this.workflowManager = IntegrityServiceManager.getWorkflowManager();
84      }
85  
86      /**
87       * Method to get the checksum errors per pillar in a given collection. 
88       * @param collectionID, the collectionID from which to return checksum errors
89       * @param pillarID, the ID of the pillar in the collection from which to return checksum errors
90       * @param pageNumber, the page number for calculating offsets (@see pageSize)
91       * @param pageSize, the number of checksum errors per page. 
92       */
93      @GET
94      @Path("/getChecksumErrorFileIDs/")
95      @Produces(MediaType.APPLICATION_JSON)
96      public List<String> getChecksumErrors(
97              @QueryParam("collectionID") String collectionID,
98              @QueryParam("pillarID") String pillarID,
99              @QueryParam("pageNumber") int pageNumber,
100             @DefaultValue("100") @QueryParam("pageSize") int pageSize) {
101         
102         int firstID = (pageNumber - 1) * pageSize;
103         int lastID = (pageNumber * pageSize) - 1;
104         List<String> ids = model.getFilesWithChecksumErrorsAtPillar(pillarID, firstID, lastID, collectionID);
105         return ids;
106     }
107     
108     /**
109      * Method to get the list of missing files per pillar in a given collection. 
110      * @param collectionID, the collectionID from which to return missing files
111      * @param pillarID, the ID of the pillar in the collection from which to return missing files
112      * @param pageNumber, the page number for calculating offsets (@see pageSize)
113      * @param pageSize, the number of checksum errors per page. 
114      */
115     @GET
116     @Path("/getMissingFileIDs/")
117     @Produces(MediaType.APPLICATION_JSON)
118     public StreamingOutput getMissingFileIDs(
119             @QueryParam("collectionID") String collectionID,
120             @QueryParam("pillarID") String pillarID,
121             @QueryParam("pageNumber") int pageNumber,
122             @DefaultValue("100") @QueryParam("pageSize") int pageSize) {
123         
124         int firstID = (pageNumber - 1) * pageSize;
125         int lastID = (pageNumber * pageSize) - 1;
126         
127         final IntegrityIssueIterator it = model.getMissingFilesAtPillarByIterator(pillarID, 
128                 firstID, lastID, collectionID);
129         
130         if(it != null) {     
131             return new StreamingOutput() {
132                 public void write(OutputStream output) throws IOException, WebApplicationException {
133                     try {
134                         boolean firstIssueWritten = false;
135                         String issue;
136                         output.write(JSON_LIST_START.getBytes());
137                         while((issue = it.getNextIntegrityIssue()) != null) {
138                             if(firstIssueWritten) {
139                                 output.write(JSON_LIST_SEPERATOR.getBytes());
140                             }
141                             String issueStr = JSON_DELIMITER + issue + JSON_DELIMITER;
142                             output.write(issueStr.getBytes());
143                             firstIssueWritten = true;
144                         }
145                         output.write(JSON_LIST_END.getBytes());
146                     } catch (Exception e) {
147                         throw new WebApplicationException(e);
148                     } finally {
149                         try {
150                             if(it != null) {
151                                 it.close();
152                             }
153                         } catch (Exception e) {
154                             log.error("Caught execption when closing IntegrityIssueIterator", e);
155                             throw new WebApplicationException(e);
156                         }
157                     }
158                 }
159             };
160         } else {
161             throw new WebApplicationException(Response.status(Response.Status.NO_CONTENT)
162                     .entity("Failed to get missing files from database")
163                     .type(MediaType.TEXT_PLAIN)
164                     .build());
165         }
166     }
167     
168     /**
169      * Method to get the list of present files on a pillar in a given collection. 
170      * @param collectionID, the collectionID from which to return present file list
171      * @param pillarID, the ID of the pillar in the collection from which to return present file list
172      * @param pageNumber, the page number for calculating offsets (@see pageSize)
173      * @param pageSize, the number of checksum errors per page. 
174      */
175     @GET
176     @Path("/getAllFileIDs/")
177     @Produces(MediaType.APPLICATION_JSON)
178     public List<String> getAllFileIDs(
179             @QueryParam("collectionID") String collectionID,
180             @QueryParam("pillarID") String pillarID,
181             @QueryParam("pageNumber") int pageNumber,
182             @DefaultValue("100") @QueryParam("pageSize") int pageSize) {
183         
184         int firstID = (pageNumber - 1) * pageSize;
185         int lastID = (pageNumber * pageSize) - 1;
186         
187         List<String> ids = model.getFilesOnPillar(pillarID, firstID, lastID, collectionID);
188         return ids;
189     }
190 
191     /**
192      * Get the listing of integrity status as a JSON array
193      */
194     @GET
195     @Path("/getIntegrityStatus/")
196     @Produces(MediaType.APPLICATION_JSON)
197     public String getIntegrityStatus(@QueryParam("collectionID") String collectionID) {
198         JSONArray array = new JSONArray();
199         List<String> pillars = SettingsUtils.getPillarIDsForCollection(collectionID);
200         Map<String, PillarStat> stats = new HashMap<String, PillarStat>();
201         for(PillarStat stat : model.getLatestPillarStats(collectionID)) {
202             if(pillars.contains(stat.getPillarID())) {
203                 stats.put(stat.getPillarID(), stat);
204             }
205         }
206         for(String pillar : pillars) {
207             if(!stats.containsKey(pillar)) {
208                 PillarStat emptyStat = new PillarStat(pillar, collectionID, 0L, 0L, 0L, 0L, 
209                         new Date(0), new Date(0));;
210                 stats.put(pillar, emptyStat);
211             }
212         }
213         for(PillarStat stat : stats.values()) {
214              array.put(makeIntegrityStatusObj(stat));
215         }
216         return array.toString();
217     }
218 
219     /***
220      * Get the current workflows setup as a JSON array 
221      */
222     @GET
223     @Path("/getWorkflowSetup/")
224     @Produces(MediaType.APPLICATION_JSON)
225     public String getWorkflowSetup(@QueryParam("collectionID") String collectionID) {
226         try {
227             JSONArray array = new JSONArray();
228             for(JobID workflowID : workflowManager.getWorkflows(collectionID)) {
229                 array.put(makeWorkflowSetupObj(workflowID));
230             }
231             return array.toString();
232         } catch (RuntimeException e) {
233             log.error("Failed to getWorkflowSetup ", e);
234             throw e;
235         }
236     }
237 
238     /**
239      * Get the list of possible workflows as a JSON array 
240      */
241     @GET
242     @Path("/getWorkflowList/")
243     @Produces(MediaType.APPLICATION_JSON)
244     public List<String> getWorkflowList(@QueryParam("collectionID") String collectionID) {
245         List<String> workflowIDs = new LinkedList<String>();
246         for(JobID workflowID : workflowManager.getWorkflows(collectionID)) {
247             workflowIDs.add(workflowID.getWorkflowName());
248         }
249         return workflowIDs;
250     }
251     
252     /**
253      * Get the latest integrity report, or an error message telling no such report found.  
254      */
255     @GET
256     @Path("/getLatestIntegrityReport/")
257     @Produces(MediaType.TEXT_PLAIN)
258     public StreamingOutput getLatestIntegrityReport(@QueryParam("collectionID") String collectionID, 
259             @QueryParam("workflowID") String workflowID) {
260         List<JobID> jobIDs = workflowManager.getWorkflows(collectionID);
261         for(JobID jobID : jobIDs) {
262             if(jobID.getWorkflowName().equals(workflowID)) {
263                 Workflow workflow = workflowManager.getWorkflow(jobID);
264                 if(workflow instanceof IntegrityCheckWorkflow) {
265                     IntegrityCheckWorkflow integrityWorkflow = (IntegrityCheckWorkflow) workflow;
266                     if(integrityWorkflow.getLatestIntegrityReport() != null) {
267                         final File report;
268                         try {
269                             report = integrityWorkflow.getLatestIntegrityReport().getReport();
270                         } catch (FileNotFoundException e) {
271                             throw new WebApplicationException(e);
272 
273                         }
274                         return new StreamingOutput() {
275                             public void write(OutputStream output) throws IOException, WebApplicationException {
276                                 try {
277                                     int i;
278                                     byte[] data = new byte[4096];
279                                     FileInputStream is = new FileInputStream(report);
280                                     while((i = is.read(data)) >= 0) {
281                                         output.write(data, 0, i);
282                                     }
283                                     is.close();
284                                 } catch (Exception e) {
285                                     throw new WebApplicationException(e);
286                                 }
287                             }
288                         };
289                     } else {
290                         throw new WebApplicationException(Response.status(Response.Status.OK)
291                                 .entity("No integrity report for workflow " + workflowID + " for collection: " 
292                                         + collectionID + " available yet")
293                                 .type(MediaType.TEXT_PLAIN)
294                                 .build());
295                     }
296                 }
297             } 
298         }
299         
300         throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND)
301                 .entity("No integrity report for workflow: " + workflowID + " for collection: " 
302                         + collectionID + " found!")
303                 .type(MediaType.TEXT_PLAIN)
304                 .build());
305     }
306 
307     /**
308      * Start a named workflow.  
309      */
310     @POST
311     @Path("/startWorkflow/")
312     @Consumes("application/x-www-form-urlencoded")
313     @Produces("text/html")
314     public String startWorkflow(@FormParam("workflowID") String workflowID,
315                                 @FormParam("collectionID") String collectionID) {
316         return workflowManager.startWorkflow(new JobID(workflowID, collectionID));
317     }
318 
319     /**
320      * Start a named workflow.  
321      */
322     @GET
323     @Path("/getCollectionInformation/")
324     @Produces(MediaType.APPLICATION_JSON)
325     public String getCollectionInformation(@QueryParam("collectionID") String collectionID) {
326         JSONObject obj = new JSONObject();
327         List<CollectionStat> stats = model.getLatestCollectionStat(collectionID, 1);
328         Date lastIngest = model.getDateForNewestFileEntryForCollection(collectionID);
329         String lastIngestStr = lastIngest == null ? "No files ingested yet" : TimeUtils.shortDate(lastIngest);
330         Long collectionSize;
331         Long numberOfFiles;
332         if(stats == null || stats.isEmpty()) {
333             collectionSize = 0L;
334             numberOfFiles = 0L;
335         } else {
336             CollectionStat stat = stats.get(0);
337             collectionSize = stat.getDataSize();
338             numberOfFiles = stat.getFileCount();
339         }
340         try {
341             obj.put("lastIngest", lastIngestStr);
342             obj.put("collectionSize", FileSizeUtils.toHumanShort(collectionSize));
343             obj.put("numberOfFiles", numberOfFiles);
344         } catch (JSONException e) {
345             obj = (JSONObject) JSONObject.NULL;
346         }
347         return obj.toString();
348     }
349     
350     private JSONObject makeIntegrityStatusObj(PillarStat stat) {
351         JSONObject obj = new JSONObject();
352         try {
353             obj.put("pillarID", stat.getPillarID());
354             obj.put("totalFileCount", stat.getFileCount());
355             obj.put("missingFilesCount", stat.getMissingFiles());
356             obj.put("checksumErrorCount", stat.getChecksumErrors());
357             return obj;
358         } catch (JSONException e) {
359             return (JSONObject) JSONObject.NULL;
360         }
361     }
362 
363     private JSONObject makeWorkflowSetupObj(JobID workflowID) {
364         JSONObject obj = new JSONObject();
365         Workflow workflow = workflowManager.getWorkflow(workflowID);
366         WorkflowStatistic lastRunStatistic = workflowManager.getLastCompleteStatistics(workflowID);
367         try {
368             obj.put("workflowID", workflowID.getWorkflowName());
369             obj.put("workflowDescription", workflow.getDescription());
370             obj.put("nextRun", TimeUtils.shortDate(workflowManager.getNextScheduledRun(workflowID)));
371             if (lastRunStatistic == null) {
372                 obj.put("lastRun", "Workflow hasn't finished a run yet");
373                 obj.put("lastRunDetails", "Workflow hasn't finished a run yet");
374             } else {
375                 obj.put("lastRun", TimeUtils.shortDate(lastRunStatistic.getFinish()));
376                 obj.put("lastRunDetails", lastRunStatistic.getFullStatistics());
377             }
378             long runInterval = workflowManager.getRunInterval(workflowID);
379             String intervalString;
380             if (runInterval == -1 ) {
381                 intervalString = "Never";
382             }  else {
383                 intervalString = TimeUtils.millisecondsToHuman(runInterval);
384             }
385             obj.put("executionInterval", intervalString);
386             obj.put("currentState", workflowManager.getCurrentStatistics(workflowID).getPartStatistics());
387             return obj;
388         } catch (JSONException e) {
389             return (JSONObject) JSONObject.NULL;
390         }
391     }
392 }