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.build.jaxb.BuildDailyProjectData;
007    import org.hackystat.dailyprojectdata.resource.build.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.analyzer.reducer.util.ReducerOptionUtility;
016    import org.hackystat.telemetry.service.server.ServerProperties;
017    import org.hackystat.utilities.time.interval.Interval;
018    import org.hackystat.utilities.time.period.Day;
019    import org.hackystat.utilities.tstamp.Tstamp;
020    
021    
022    /**
023     * Returns a single stream providing Build data.
024     * <p>
025     * Accepts the following options in the following order.
026     * <ol>
027     *  <li> User: Supply a user email to get Build counts for just that user. 
028     *       Default is "*" indicating the aggregate Build Data for all project members.
029     *  <li> Result: One of Success, Failure, or *, indicating whether the count is
030     *       just of successful builds, failed builds, or all builds. Default is "*". 
031     *  <li> Type: A string to restrict the counts to those builds with a "Type" property
032     *       matching this string, or "*" to indicate all builds regardless of Type. 
033     *       Default is "*".
034     *  <li> isCumulative: True or false. Default is false.
035     * </ol>
036     * 
037     * @author Philip Johnson
038     */
039    public class BuildReducer implements TelemetryReducer {
040     
041      /**
042       * Computes and returns the required telemetry streams object.
043       *
044       * @param project The project.
045       * @param dpdClient The DPD Client.
046       * @param interval The interval.
047       * @param options The optional parameters.
048       *
049       * @return Telemetry stream collection.
050       * @throws TelemetryReducerException If there is any error.
051       */
052      public TelemetryStreamCollection compute(Project project, DailyProjectDataClient dpdClient, 
053          Interval interval, String[] options) throws TelemetryReducerException {
054        // weird. for some reason we want 'null' as default rather than '*' etc.
055        String member = null;
056        String result = null;
057        String type = null;
058        boolean isCumulative = false;
059        //process options
060        if (options.length > 4) {
061          throw new TelemetryReducerException("Build reducer takes 4 optional parameters.");
062        }
063    
064        if (options.length >= 1 && !"*".equals(options[0])) {
065          member = options[0];
066        }
067    
068        if (options.length >= 2 && !"*".equals(options[1])) {
069          result = options[1];
070        }
071        
072        if (options.length >= 3 && !"*".equals(options[2])) {
073          type = options[2];
074        }
075        
076        if (options.length >= 4) {
077          isCumulative = ReducerOptionUtility.parseBooleanOption(4, options[3]);
078        }
079        
080        // Find out the DailyProjectData host, throw error if not found.
081        String dpdHost = System.getProperty(ServerProperties.DAILYPROJECTDATA_FULLHOST_KEY);
082        if (dpdHost == null) {
083          throw new TelemetryReducerException("Null DPD host in BuildReducer");
084        }
085    
086        // now compute the single telemetry stream. 
087        try {
088          TelemetryStream telemetryStream = this.getStream(dpdClient, project, interval,  
089              member, result, type, isCumulative, null);
090          TelemetryStreamCollection streams = new TelemetryStreamCollection(null, project, interval);
091          streams.add(telemetryStream);
092          return streams;
093        } 
094        catch (Exception e) {
095          throw new TelemetryReducerException(e);
096        }
097      }
098    
099      /**
100       * Gets the telemetry stream.
101       * 
102       * @param dpdClient The DailyProjectData client we will contact for the data. 
103       * @param project The project.
104       * @param interval The interval.
105       * @param member Project member, or null to match all members. 
106       * @param result The build result, either FAILURE, SUCCESS, or null to match both.
107       * @param type The value of the build 'Type' property, a string or null to match anything.
108       * @param isCumulative True for cumulative measure.
109       * @param streamTagValue The tag for the generated telemetry stream.
110       * 
111       * @return The telemetry stream as required.
112       * @throws Exception If there is any error.
113       */
114      TelemetryStream getStream(DailyProjectDataClient dpdClient, 
115          Project project, Interval interval, String member, String result, 
116          String type, boolean isCumulative, Object streamTagValue) 
117            throws Exception {
118        TelemetryStream telemetryStream = new TelemetryStream(streamTagValue);
119        List<IntervalUtility.Period> periods = IntervalUtility.getPeriods(interval);
120        long cumulativeBuilds = 0;
121    
122        for (IntervalUtility.Period period : periods) {
123          Long value = this.getData(dpdClient, project, period.getStartDay(), period.getEndDay(),
124              member, result, type);
125          
126          if (value != null) {
127            cumulativeBuilds += value;
128          }
129          
130          if (isCumulative) {
131            telemetryStream.addDataPoint(new TelemetryDataPoint(period.getTimePeriod(), 
132                cumulativeBuilds));        
133          }
134          else {
135            telemetryStream.addDataPoint(new TelemetryDataPoint(period.getTimePeriod(), value));
136          }
137        }
138        return telemetryStream;
139      }
140      
141      /**
142       * Returns a Build count for the specified time interval, or null if no SensorData. 
143       *
144       * @param dpdClient The DailyProjectData client we will use to get this data. 
145       * @param project The project.
146       * @param startDay The start day (inclusive).
147       * @param endDay The end day (inclusive).
148       * @param member The project member email, or null to match all members. 
149       * @param result The result, either SUCCESS, FAILURE, or null for both.
150       * @param type The 'Type' property, or null to match anything.
151       * @throws TelemetryReducerException If anything goes wrong.
152       *
153       * @return The Build count, or null if there is no Build SensorData for that time period. 
154       */
155      Long getData(DailyProjectDataClient dpdClient, Project project, Day startDay, Day endDay, 
156          String member, String result, String type) throws TelemetryReducerException {
157        long buildCount = 0;
158        String typeString = (type == null) ? "*" : type;
159        try {
160          // For each day in the interval... 
161          for (Day day = startDay; day.compareTo(endDay) <= 0; day = day.inc(1) ) {
162            // Get the DPD for the required 'Type' property.
163            BuildDailyProjectData data = 
164              dpdClient.getBuild(project.getOwner(), project.getName(), Tstamp.makeTimestamp(day), 
165                  typeString);
166            // Go through the DPD per-member data...
167            for (MemberData memberData : data.getMemberData()) {
168              // Check to see if this data is for the given member and of the appropriate type.
169              if ((member == null) || "*".equals(member) || 
170                  (memberData.getMemberUri().endsWith(member))) {
171                if ((result == null) || "*".equals(result)) {
172                  buildCount += memberData.getFailure() + memberData.getSuccess();
173                }
174                else if ("Success".equals(result)) {
175                  buildCount += memberData.getSuccess();
176                }
177                else if ("Failure".equals(result)) {
178                  buildCount += memberData.getFailure();
179                }
180              }
181            }
182          }
183        }
184        catch (Exception ex) {
185          throw new TelemetryReducerException(ex);
186        }
187    
188        //Return null if no data, the Build count data otherwise. 
189        return (buildCount > 0) ? Long.valueOf(buildCount) : null; 
190      }
191    
192    }