001    package org.hackystat.tickertape.datasource.hackystat;
002    
003    import java.util.HashSet;
004    import java.util.Set;
005    
006    import javax.xml.datatype.XMLGregorianCalendar;
007    
008    import org.hackystat.dailyprojectdata.client.DailyProjectDataClient;
009    import org.hackystat.dailyprojectdata.client.DailyProjectDataClientException;
010    import org.hackystat.dailyprojectdata.resource.coverage.jaxb.ConstructData;
011    import org.hackystat.dailyprojectdata.resource.coverage.jaxb.CoverageDailyProjectData;
012    import org.hackystat.dailyprojectdata.resource.filemetric.jaxb.FileData;
013    import org.hackystat.dailyprojectdata.resource.filemetric.jaxb.FileMetricDailyProjectData;
014    import org.hackystat.sensorbase.client.SensorBaseClient;
015    import org.hackystat.sensorbase.client.SensorBaseClientException;
016    import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataIndex;
017    import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataRef;
018    import org.hackystat.utilities.tstamp.Tstamp;
019    
020    /**
021     * A datasource corresponding to Hackystat services (sensorbase and dailyprojectdata). 
022     * @author Philip Johnson
023     */
024    public class HackystatProject {
025    
026      /** The sensorbase host URL, such as http://dasha.ics.hawaii.edu:9876/sensorbase. */
027      private String sensorbaseHost;
028      /** The dailyprojectdata host URL, such as http://dasha.ics.hawaii.edu:9877/dailyprojectdata. */
029      private String dpdHost;
030      /** The user used to get data, such as johnson@hawaii.edu. */
031      private String user;
032      /** The password for this user account. */
033      private String password;
034      /** The project whose data will be retrieved. */
035      private String projectName;
036      /** The owner of the project. */
037      private String projectOwner;
038      /** The interval in minutes for getting data. */
039      private int interval;
040    
041      /**
042       * Creates a new Hackystat instance, initializing the state. 
043       * @param sensorbaseHost  The sensorbase host. 
044       * @param dailyprojectdataHost  The dpd host.
045       * @param user The sensorbase user. 
046       * @param password The user's password.
047       * @param projectName A project for which this user has access.
048       * @param projectOwner The project's owner.  
049       * @param interval The interval in minutes for which we should ask for data. 
050       */
051      public HackystatProject(String sensorbaseHost, String dailyprojectdataHost, String user, 
052          String password, String projectName, String projectOwner, int interval) {
053        this.sensorbaseHost = sensorbaseHost;
054        this.dpdHost = dailyprojectdataHost;
055        this.user = user;
056        this.password = password;
057        this.projectName = projectName;
058        this.projectOwner = projectOwner;
059        this.interval = interval;
060      }
061      
062      /**
063       * Contacts Hackystat services, gets data, and returns a summary as a string. 
064       * @return The summary of Hackystat activity. 
065       */
066      public String getInfo() {
067        // First, validate that we are authorized for these Hackystat services. 
068        SensorBaseClient sensorbaseClient;
069        DailyProjectDataClient dpdClient;
070        try {
071          sensorbaseClient = new SensorBaseClient(sensorbaseHost, user, password);
072          dpdClient = new DailyProjectDataClient(dpdHost, user, password);
073          sensorbaseClient.authenticate();
074          dpdClient.authenticate();
075        }
076        catch (Exception e) {
077          return "Error getting Hackystat info. Message is: " + e.getMessage();
078        }
079        // Second, validate that we can get info about the specified project. 
080        try {
081         sensorbaseClient.inProject(projectName, projectOwner);
082        }
083        catch (Exception e) {
084          return String.format("%s is not in project %s", user, projectName);
085        }
086        
087        // We're OK, so figure out the time interval of interest. 
088        XMLGregorianCalendar endTime = Tstamp.makeTimestamp();
089        XMLGregorianCalendar startTime = Tstamp.incrementMinutes(endTime, (-1 * interval));
090        
091        // Now get information.
092        StringBuffer info = new StringBuffer();
093        info.append(getDevEventInfo(sensorbaseClient, startTime, endTime)).append(' ');
094        info.append(getCommitInfo(sensorbaseClient, startTime, endTime)).append(' ');
095        info.append(getUnitTestInfo(sensorbaseClient, startTime, endTime)).append(' ');
096        info.append(getBuildInfo(sensorbaseClient, startTime, endTime)).append(' ');
097        info.append(getCoverageInfo(sensorbaseClient, dpdClient, startTime, endTime)).append(' ');
098        info.append(getSizeInfo(sensorbaseClient, dpdClient, startTime, endTime)).append(' ');
099        
100        // Return the info we've found.
101        return info.toString().trim().replace("  ", " ");
102      }
103      
104    
105      /**
106       * Returns a summary string regarding DevEvent info, or the empty string if no info available.
107       * @param client The SensorBaseClient. 
108       * @param startTime The start time.
109       * @param endTime The end time.
110       * @return The summary string, or an empty string. 
111       */
112      private String getDevEventInfo(SensorBaseClient client, XMLGregorianCalendar startTime, 
113          XMLGregorianCalendar endTime) {
114        String message = ""; 
115        try {
116          SensorDataIndex index = client.getProjectSensorData(projectOwner, projectName, 
117              startTime, endTime, "DevEvent");
118          message = getWorkers(index, "been editing files.");
119        }
120        catch (Exception e) {
121          message = "Error retrieving DevEvent data. ";
122        }
123        return message;
124      }
125      
126      /**
127       * Returns a summary string regarding Commit info, or the empty string if no info available.
128       * @param client The SensorBaseClient. 
129       * @param startTime The start time.
130       * @param endTime The end time.
131       * @return The summary string, or an empty string. 
132       */
133      private String getCommitInfo(SensorBaseClient client, XMLGregorianCalendar startTime, 
134          XMLGregorianCalendar endTime) {
135        String message = ""; 
136        try {
137          SensorDataIndex index = client.getProjectSensorData(projectOwner, projectName, 
138              startTime, endTime, "Commit");
139          message = getWorkers(index, "committed files.");
140        }
141        catch (Exception e) {
142          message = "Error retrieving Commit data. ";
143        }
144        return message;
145      }
146      
147      /**
148       * Returns a summary string regarding Commit info, or the empty string if no info available.
149       * @param client The SensorBaseClient. 
150       * @param startTime The start time.
151       * @param endTime The end time.
152       * @return The summary string, or an empty string. 
153       */
154      private String getUnitTestInfo(SensorBaseClient client, XMLGregorianCalendar startTime, 
155          XMLGregorianCalendar endTime) {
156        String message = ""; 
157        try {
158          SensorDataIndex index = client.getProjectSensorData(projectOwner, projectName, 
159              startTime, endTime, "UnitTest");
160          message = getWorkers(index, "run tests.");
161        }
162        catch (Exception e) {
163          message = "Error retrieving UnitTest data. ";
164        }
165        return message;
166      }
167      
168      /**
169       * Returns a summary string regarding Build info, or the empty string if no info available.
170       * @param client The SensorBaseClient. 
171       * @param startTime The start time.
172       * @param endTime The end time.
173       * @return The summary string, or an empty string. 
174       */
175      private String getBuildInfo(SensorBaseClient client, XMLGregorianCalendar startTime, 
176          XMLGregorianCalendar endTime) {
177        String message = ""; 
178        try {
179          SensorDataIndex index = client.getProjectSensorData(projectOwner, projectName, 
180              startTime, endTime, "Build");
181          message = getWorkers(index, "built the system.");
182        }
183        catch (Exception e) {
184          message = "Error retrieving Build data. ";
185        }
186        return message;
187      }
188      
189      /**
190       * Returns a summary string regarding Build info, or the empty string if no info available.
191       * @param client The SensorBaseClient. 
192       * @param dpdClient The DailyProjectData client. 
193       * @param startTime The start time.
194       * @param endTime The end time.
195       * @return The summary string, or an empty string. 
196       */
197      private String getCoverageInfo(SensorBaseClient client, DailyProjectDataClient dpdClient, 
198          XMLGregorianCalendar startTime, XMLGregorianCalendar endTime) {
199        String message = ""; 
200        try {
201          SensorDataIndex index = client.getProjectSensorData(projectOwner, projectName, 
202              startTime, endTime, "Coverage");
203          if (!index.getSensorDataRef().isEmpty()) {
204            CoverageDailyProjectData dpd = dpdClient.getCoverage(projectOwner, projectName, startTime,
205                "Line");
206            double covered = 0.0;
207            double total = 0.0;
208            for (ConstructData data : dpd.getConstructData()) {
209              covered += data.getNumCovered();
210              total += data.getNumCovered() + data.getNumUncovered(); 
211            }
212            int percentCovered = (total == 0) ? 0 : (int)((covered / total) * 100);
213            message = String.format("Line coverage is %s percent.", percentCovered);  
214          }
215        }
216        catch (SensorBaseClientException e) {
217          message = "Error retrieving Coverage sensor data. ";
218        }
219        catch (DailyProjectDataClientException e) {
220          message = "Error retrieving Coverage DPD data. ";
221        }
222        return message;
223      }
224      
225      /**
226       * Returns a summary string regarding Size info, or the empty string if no info available.
227       * @param client The SensorBaseClient. 
228       * @param dpdClient The DailyProjectData client. 
229       * @param startTime The start time.
230       * @param endTime The end time.
231       * @return The summary string, or an empty string. 
232       */
233      private String getSizeInfo(SensorBaseClient client, DailyProjectDataClient dpdClient, 
234          XMLGregorianCalendar startTime, XMLGregorianCalendar endTime) {
235        String message = ""; 
236        try {
237          SensorDataIndex index = client.getProjectSensorData(projectOwner, projectName, 
238              startTime, endTime, "FileMetric");
239          if (!index.getSensorDataRef().isEmpty()) {
240            FileMetricDailyProjectData dpd = dpdClient.getFileMetric(projectOwner, projectName, 
241                startTime, "TotalLines");
242            int totalLines = 0;
243            for (FileData data : dpd.getFileData()) {
244              totalLines += data.getSizeMetricValue();
245            }
246            message = String.format("The system has %s lines of code.", totalLines);
247          }
248        }
249        catch (SensorBaseClientException e) {
250          message = "Error retrieving FileMetric sensor data. ";
251        }
252        catch (DailyProjectDataClientException e) {
253          message = "Error retrieving FileMetric DPD data. ";
254        }
255        return message;
256      }
257      
258    
259      /**
260       * Returns a string naming the users in the passed index who have been working on the specified
261       * activity, or the empty string if there is nothing in the index.
262       * @param index The index. 
263       * @param activity the activity.
264       * @return The string, possibly empty.
265       */
266      private String getWorkers (SensorDataIndex index, String activity) {
267        // Find all of the developers who have been committing during this interval.
268        Set<String> workers = new HashSet<String>();
269        String message = "";
270        for (SensorDataRef ref : index.getSensorDataRef()) {
271          workers.add(ref.getOwner());
272        }
273        // Create a message indicating who has been working
274        if (!workers.isEmpty()) {
275          StringBuffer buff = new StringBuffer();
276          for (String worker : workers) {
277            //String newWorker = worker.replace("@", "+at+").replace(".", "+dot+");
278            buff.append(worker).append(' ');
279          }
280          message = buff.toString() + 
281          ((workers.size() == 1) ? " has " : " have ") + activity + " ";
282        }
283        return message; 
284      }
285    }