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.devtime.jaxb.DevTimeDailyProjectData;
007    import org.hackystat.dailyprojectdata.resource.devtime.jaxb.MemberData;
008    import org.hackystat.sensorbase.resource.projects.jaxb.Project;
009    import org.hackystat.sensorbase.uripattern.UriPattern;
010    import org.hackystat.telemetry.analyzer.model.TelemetryDataPoint;
011    import org.hackystat.telemetry.analyzer.model.TelemetryStream;
012    import org.hackystat.telemetry.analyzer.model.TelemetryStreamCollection;
013    import org.hackystat.telemetry.analyzer.reducer.TelemetryReducer;
014    import org.hackystat.telemetry.analyzer.reducer.TelemetryReducerException;
015    import org.hackystat.telemetry.analyzer.reducer.util.IntervalUtility;
016    import org.hackystat.telemetry.analyzer.reducer.util.ReducerOptionUtility;
017    import org.hackystat.telemetry.service.server.ServerProperties;
018    import org.hackystat.utilities.time.interval.Interval;
019    import org.hackystat.utilities.time.period.Day;
020    import org.hackystat.utilities.tstamp.Tstamp;
021    
022    
023    /**
024     * Returns a single stream providing DevTime data in hours.
025     * <p>
026     * Accepts the following options in the following order, although only User and isCumulative
027     * are supported at the current time.
028     * <ol>
029     * <li> EventType: Supply an Event Type to restrict the DevTime to just the time 
030     *      associated with that Event Type. 
031     *      Default is "*" which indicates all file types are used in computing the 
032     *      DevTime.  
033     *  <li> User: Supply a user email to get DevTime for just that user. 
034     *       Default is "*" indicating the aggregate DevTime of all project members.
035     *  <li> ResourceFilterPattern: Restricts the files over which the DevTime 
036     *       is computed. Default is "**".
037     *  <li> isCumulative: True or false. Default is false.
038     * </ol>
039     * 
040     * @author Hongbing Kou, Philip Johnson
041     */
042    public class DevTimeReducer implements TelemetryReducer {
043     
044    
045      /**
046       * Computes and returns the required telemetry streams object.
047       *
048       * @param project The project.
049       * @param dpdClient The DPD Client.
050       * @param interval The interval.
051       * @param options The optional parameters.
052       *
053       * @return Telemetry stream collection.
054       * @throws TelemetryReducerException If there is any error.
055       */
056      public TelemetryStreamCollection compute(Project project, DailyProjectDataClient dpdClient, 
057          Interval interval, String[] options) throws TelemetryReducerException {
058        // weird. for some reason we want 'null' as default rather than '*' etc.
059        String eventType = null;
060        String member = null;
061        UriPattern resourcePattern = null;
062        boolean isCumulative = false;
063        //process options
064        if (options.length > 4) {
065          throw new TelemetryReducerException("DevTime reducer takes only 4 optional parameters.");
066        }
067    
068        if (options.length >= 1 && ! "*".equals(options[0])) {
069          eventType = options[0];
070          eventType = eventType.toLowerCase();
071        }
072    
073        if (options.length >= 2 && ! "*".equals(options[1])) {
074          member = options[1];
075        }
076        
077        if (options.length >= 3) {
078          resourcePattern = new UriPattern(options[2]);
079        }
080        
081        if (options.length >= 4) {
082          isCumulative = ReducerOptionUtility.parseBooleanOption(4, options[3]);
083        }
084        
085        // Find out the DailyProjectData host, throw error if not found.
086        String dpdHost = System.getProperty(ServerProperties.DAILYPROJECTDATA_FULLHOST_KEY);
087        if (dpdHost == null) {
088          throw new TelemetryReducerException("Null DPD host in DevTimeReducer");
089        }
090    
091        // now compute the single telemetry stream. Remember, we only process member and Cumulative.
092        try {
093          TelemetryStream telemetryStream = this.getStream(dpdClient, project, interval,  
094              eventType, member, resourcePattern, isCumulative, null);
095          TelemetryStreamCollection streams = new TelemetryStreamCollection(null, project, interval);
096          streams.add(telemetryStream);
097          return streams;
098        } 
099        catch (Exception e) {
100          throw new TelemetryReducerException(e);
101        }
102      }
103    
104      /**
105       * Gets the telemetry stream.
106       * 
107       * @param dpdClient The DailyProjectData client we will contact for the data. 
108       * @param project The project.
109       * @param interval The interval.
110       * @param eventType The event type, or null to match all event types. (ignored)
111       * @param member Project member, or null to match all members. 
112       * @param filePattern File filter pattern, or null to match all files. (ignored)
113       * @param isCumulative True for cumulative measure.
114       * @param streamTagValue The tag for the generated telemetry stream.
115       * 
116       * @return The telemetry stream as required.
117       * @throws Exception If there is any error.
118       */
119      TelemetryStream getStream(DailyProjectDataClient dpdClient, 
120          Project project, Interval interval, String eventType, String member, 
121          UriPattern filePattern, boolean isCumulative, Object streamTagValue) 
122            throws Exception {
123        TelemetryStream telemetryStream = new TelemetryStream(streamTagValue);
124        List<IntervalUtility.Period> periods = IntervalUtility.getPeriods(interval);
125        double cumulativeDevTime = 0;
126    
127        for (IntervalUtility.Period period : periods) {
128          Double value = this.getData(dpdClient, project, period.getStartDay(), period.getEndDay(),
129              eventType, member, filePattern);
130          
131          if (value != null) {
132            cumulativeDevTime += value;
133          }
134          
135          if (isCumulative) {
136            telemetryStream.addDataPoint(new TelemetryDataPoint(period.getTimePeriod(), 
137                cumulativeDevTime));        
138          }
139          else {
140            telemetryStream.addDataPoint(new TelemetryDataPoint(period.getTimePeriod(), value));
141          }
142        }
143        return telemetryStream;
144      }
145      
146      /**
147       * Returns a DevTime value for the specified time interval, or null if no SensorData. 
148       * Note that the DPD returns DevTime in minutes, but we are going to convert it to hours
149       * since that makes more sense for telemetry.
150       * 
151       * NOTE: This function currently ignores the filePattern and eventType constraints and simply
152       * returns either total aggregate devTime (if member is null), or the devTime for a single
153       * member (if the memberEmail is supplied).
154       *
155       * @param dpdClient The DailyProjectData client we will use to get this data. 
156       * @param project The project.
157       * @param startDay The start day (inclusive).
158       * @param endDay The end day (inclusive).
159       * @param eventType The event type, or null to match all file types. (ignored)
160       * @param member The project member email, or null to match all members. 
161       * @param filePattern File filter pattern, or null to match all files. (ignored)
162       * @throws TelemetryReducerException If anything goes wrong.
163       *
164       * @return The DevTime, or null if there is no DevEvent SensorData for that time period. 
165       */
166      Double getData(DailyProjectDataClient dpdClient, Project project, Day startDay, Day endDay, 
167          String eventType, String member, UriPattern filePattern) throws TelemetryReducerException {
168        double devTime = 0;
169        try {
170          // For each day in the interval... 
171          for (Day day = startDay; day.compareTo(endDay) <= 0; day = day.inc(1) ) {
172            // Get the DPD...
173            DevTimeDailyProjectData data = 
174              dpdClient.getDevTime(project.getOwner(), project.getName(), Tstamp.makeTimestamp(day));
175            // Go through the DPD per-member data...
176            for (MemberData memberData : data.getMemberData()) {
177              // If this DPD memberdata's owner matches who we want
178              if ((member == null) || "*".equals(member) || 
179                  (memberData.getMemberUri().endsWith(member))) {
180                devTime += memberData.getDevTime().doubleValue();
181              }
182            }
183          }
184        }
185        catch (Exception ex) {
186          throw new TelemetryReducerException(ex);
187        }
188    
189        //Return null if no data, the DevTime data otherwise. 
190        return (devTime > 0) ? new Double((devTime / 60)) : null; 
191      }
192    
193    }