001    package org.hackystat.telemetry.analyzer.reducer.impl;
002    
003    import java.util.List;
004    
005    import org.hackystat.dailyprojectdata.client.DailyProjectDataClient;
006    import org.hackystat.dailyprojectdata.resource.commit.jaxb.CommitDailyProjectData;
007    import org.hackystat.dailyprojectdata.resource.commit.jaxb.MemberData;
008    import org.hackystat.sensorbase.resource.projects.jaxb.Project;
009    import org.hackystat.telemetry.analyzer.model.TelemetryDataPoint;
010    import org.hackystat.telemetry.analyzer.model.TelemetryStream;
011    import org.hackystat.telemetry.analyzer.model.TelemetryStreamCollection;
012    import org.hackystat.telemetry.analyzer.reducer.TelemetryReducer;
013    import org.hackystat.telemetry.analyzer.reducer.TelemetryReducerException;
014    import org.hackystat.telemetry.analyzer.reducer.util.IntervalUtility;
015    import org.hackystat.telemetry.service.server.ServerProperties;
016    import org.hackystat.utilities.time.interval.Interval;
017    import org.hackystat.utilities.time.period.Day;
018    import org.hackystat.utilities.tstamp.Tstamp;
019    
020    
021    /**
022     * Returns a single stream providing Churn data.
023     * Churn is linesAdded + linesDeleted. 
024     * <p>
025     * Options:
026     * <ol>
027     * <li> member: The project member whose churn data is to be returned, or "*" for all members.
028     * <li> isCumulative: True or false. Default is false.
029     * </ol>
030     * 
031     * @author Philip Johnson
032     */
033    public class ChurnReducer implements TelemetryReducer { 
034     
035      /**
036       * Computes and returns the required telemetry streams object.
037       *
038       * @param project The project.
039       * @param dpdClient The DPD Client.
040       * @param interval The interval.
041       * @param options The optional parameters.
042       *
043       * @return Telemetry stream collection.
044       * @throws TelemetryReducerException If there is any error.
045       */
046      public TelemetryStreamCollection compute(Project project, DailyProjectDataClient dpdClient, 
047          Interval interval, String[] options) throws TelemetryReducerException {
048        String member = null;
049        boolean isCumulative = false;
050        //process options
051        if (options.length > 2) {
052          throw new TelemetryReducerException("Churn reducer takes 2 optional parameters.");
053        }
054        if (options.length >= 1) {
055          member = options[0];
056        }
057        
058        if (options.length >= 2) {
059          try {
060            isCumulative = Boolean.valueOf(options[1]);
061          }
062          catch (Exception e) {
063            throw new TelemetryReducerException("Illegal cumulative value.", e);
064          }
065        }
066        
067        // Find out the DailyProjectData host, throw error if not found.
068        String dpdHost = System.getProperty(ServerProperties.DAILYPROJECTDATA_FULLHOST_KEY);
069        if (dpdHost == null) {
070          throw new TelemetryReducerException("Null DPD host in ChurnReducer");
071        }
072    
073        // now get the telemetry stream. 
074        try {
075          TelemetryStream telemetryStream = this.getStream(dpdClient, project, interval,  
076              member, isCumulative, null);
077          TelemetryStreamCollection streams = new TelemetryStreamCollection(null, project, interval);
078          streams.add(telemetryStream);
079          return streams;
080        } 
081        catch (Exception e) {
082          throw new TelemetryReducerException(e);
083        }
084      }
085    
086      /**
087       * Gets the telemetry stream.
088       * 
089       * @param dpdClient The DailyProjectData client we will contact for the data. 
090       * @param project The project.
091       * @param interval The interval.
092       * @param member The member, or "*" for all members.
093       * @param isCumulative True for cumulative measure.
094       * @param streamTagValue The tag for the generated telemetry stream.
095       * 
096       * @return The telemetry stream as required.
097       * 
098       * @throws Exception If there is any error.
099       */
100      TelemetryStream getStream(DailyProjectDataClient dpdClient, 
101          Project project, Interval interval,
102          String member, boolean isCumulative, Object streamTagValue) 
103            throws Exception {
104        TelemetryStream telemetryStream = new TelemetryStream(streamTagValue);
105        List<IntervalUtility.Period> periods = IntervalUtility.getPeriods(interval);
106        long cumulativeCount = 0;
107        
108        for (IntervalUtility.Period period : periods) {
109          Long value = this.getData(dpdClient, project, period.getStartDay(), period.getEndDay(),
110              member);
111          
112          if (value != null) {
113            cumulativeCount += value;
114          }
115          
116          if (isCumulative) {
117            telemetryStream.addDataPoint(new TelemetryDataPoint(period.getTimePeriod(), 
118                cumulativeCount));        
119          }
120          else {
121            telemetryStream.addDataPoint(new TelemetryDataPoint(period.getTimePeriod(), value));
122          }
123        }
124        return telemetryStream;
125      }
126      
127      /**
128       * Returns a Churn value for the specified time interval, or null if no Churn SensorData. 
129       * 
130       * @param dpdClient The DailyProjectData client we will use to get this data. 
131       * @param project The project.
132       * @param startDay The start day (inclusive).
133       * @param endDay The end day (inclusive).
134       * @param member The member email, or "*" for all members.
135       * @throws TelemetryReducerException If anything goes wrong.
136       *
137       * @return The Churn count, or null if there is no Commit SensorData for that time period. 
138       */
139      Long getData(DailyProjectDataClient dpdClient, Project project, Day startDay, Day endDay, 
140          String member) throws TelemetryReducerException {
141        long count = 0;
142        boolean hasData = false;
143        try {
144          // For each day in the interval... 
145          for (Day day = startDay; day.compareTo(endDay) <= 0; day = day.inc(1) ) {
146            // Get the DPD...
147            CommitDailyProjectData data = 
148              dpdClient.getCommit(project.getOwner(), project.getName(), Tstamp.makeTimestamp(day));
149            // Go through the DPD per-member data...
150            for (MemberData memberData : data.getMemberData()) {
151              if ((member == null) || "*".equals(member) || 
152                  (memberData.getMemberUri().endsWith(member))) {
153                hasData = true;
154                count += memberData.getLinesAdded() + memberData.getLinesDeleted() +
155                memberData.getLinesModified();
156              }
157            }
158          }
159        }
160        catch (Exception ex) {
161          throw new TelemetryReducerException(ex);
162        }
163    
164        //Return null if no data, the Churn counts otherwise. 
165        return (hasData) ? Long.valueOf(count) : null; 
166      }
167    
168    }