001    package org.hackystat.projectbrowser.page.projectportfolio.detailspanel;
002    
003    import java.io.File;
004    import java.io.FileInputStream;
005    import java.io.FileNotFoundException;
006    import java.io.InputStream;
007    import java.io.Serializable;
008    import java.util.ArrayList;
009    import java.util.Arrays;
010    import java.util.Collections;
011    import java.util.Comparator;
012    import java.util.HashMap;
013    import java.util.List;
014    import java.util.Map;
015    import java.util.logging.Logger;
016    import javax.xml.bind.JAXBContext;
017    import javax.xml.bind.JAXBException;
018    import javax.xml.bind.Unmarshaller;
019    import javax.xml.datatype.XMLGregorianCalendar;
020    import javax.xml.transform.stream.StreamSource;
021    import javax.xml.validation.Schema;
022    import javax.xml.validation.SchemaFactory;
023    import org.apache.wicket.PageParameters;
024    import org.apache.wicket.model.IModel;
025    import org.apache.wicket.model.Model;
026    import org.hackystat.projectbrowser.ProjectBrowserApplication;
027    import org.hackystat.projectbrowser.ProjectBrowserProperties;
028    import org.hackystat.projectbrowser.ProjectBrowserSession;
029    import org.hackystat.projectbrowser.page.loadingprocesspanel.Processable;
030    import org.hackystat.projectbrowser.page.projectportfolio.detailspanel.chart.
031    StreamDeviationClassifier;
032    import org.hackystat.projectbrowser.page.projectportfolio.detailspanel.chart.MiniBarChart;
033    import org.hackystat.projectbrowser.page.projectportfolio.detailspanel.chart.StreamClassifier;
034    import org.hackystat.projectbrowser.page.projectportfolio.detailspanel.chart.
035            StreamParticipationClassifier;
036    import org.hackystat.projectbrowser.page.projectportfolio.detailspanel.chart.StreamTrendClassifier;
037    import org.hackystat.projectbrowser.page.projectportfolio.jaxb.Measures;
038    import org.hackystat.projectbrowser.page.projectportfolio.jaxb.PortfolioDefinitions;
039    import org.hackystat.projectbrowser.page.projectportfolio.jaxb.Measures.Measure;
040    import org.hackystat.projectbrowser.page.telemetry.TelemetrySession;
041    import org.hackystat.sensorbase.resource.projects.ProjectUtils;
042    import org.hackystat.sensorbase.resource.projects.jaxb.Project;
043    import org.hackystat.telemetry.analyzer.configuration.ExtensionFileFilter;
044    import org.hackystat.telemetry.service.client.TelemetryClient;
045    import org.hackystat.telemetry.service.client.TelemetryClientException;
046    import org.hackystat.telemetry.service.resource.chart.jaxb.ParameterDefinition;
047    import org.hackystat.telemetry.service.resource.chart.jaxb.TelemetryChartData;
048    import org.hackystat.telemetry.service.resource.chart.jaxb.TelemetryChartRef;
049    import org.hackystat.utilities.logger.HackystatLogger;
050    import org.hackystat.utilities.tstamp.Tstamp;
051    import org.hackystat.utilities.uricache.UriCache;
052    import org.xml.sax.SAXException;
053    
054    /**
055     * Data model to hold state of Project Portfolio.
056     * 
057     * @author Shaoxuan Zhang
058     */
059    public class ProjectPortfolioDataModel implements Serializable, Processable {
060    
061      /** Support serialization. */
062      private static final long serialVersionUID = 5465041655927215391L;
063      /** state of data loading process. */
064      private volatile boolean inProcess = false;
065      /** result of data loading. */
066      private volatile boolean complete = false;
067      /** message to display when data loading is in process. */
068      private String processingMessage = "";
069    
070      /** host of the telemetry host. */
071      private String telemetryHost;
072      /** email of the user. */
073      private String userEmail;
074      /** password of the user. */
075      private String password;
076      /** The telemetry session. */
077      private TelemetrySession telemetrySession;
078    
079      /** the granularity this data model focus. */
080      private String granularity = "Week";
081      /** If current day, week or month will be included in portfolio. */
082      // private boolean includeCurrentWeek = true;
083      /** The start date this user has selected. */
084      private long startDate = 0;
085      /** The end date this user has selected. */
086      private long endDate = 0;
087      /** List of available stream classifiers. */
088      public static final List<String> availableClassifiers = Arrays.asList(new String[]{
089                                                      "None",
090                                                      StreamTrendClassifier.name,
091                                                      StreamParticipationClassifier.name,
092                                                      StreamDeviationClassifier.name
093                                                  });
094    
095      /**
096       * the time phrase this data model focus. In scale of telemetryGranularity, from current to the
097       * past.
098       */
099      // private int timePhrase = 5;
100      /** The projects this user has selected. */
101      private List<Project> selectedProjects = new ArrayList<Project>();
102      /** The charts in this model. */
103      private Map<Project, List<MiniBarChart>> measuresCharts = 
104        new HashMap<Project, List<MiniBarChart>>();
105      /** The List of PortfolioMeasureConfiguration. */
106      private final List<PortfolioMeasureConfiguration> measures = 
107        new ArrayList<PortfolioMeasureConfiguration>();
108      /** Alias for measure. Maps names from definition to names for display. */
109      //private final Map<String, String> measureAlias = new HashMap<String, String>();
110      /** The portfolio measure configuration loaded from xml file. */
111      private PortfolioDefinitions portfolioDefinitions = getPortfolioDefinitions();
112      /** The configuration saving capacity. */
113      private static Long capacity = 1000L;
114      /** The max life of the saved configuration. */
115      private static Double maxLife = 300.0;
116    
117      /** The background color for table cells. */
118      private String backgroundColor = "000000";
119      /** The font color for table cells. */
120      private String fontColor = "ffffff";
121      /** The font color for N/A. */
122      private String naColor = "888888";
123      /** The color for good state. */
124      private String goodColor = "00ff00";
125      /** The color for soso state. */
126      private String averageColor = "ffff00";
127      /** The color for bad state. */
128      private String poorColor = "ff0000";
129    
130      /**
131       * Constructor that initialize the measures.
132       * 
133       * @param telemetryHost the telemetry host
134       * @param userEmail the user's email
135       * @param password the user's passowrd
136       */
137      public ProjectPortfolioDataModel(String telemetryHost, String userEmail, String password) {
138        telemetrySession = ProjectBrowserSession.get().getTelemetrySession();
139        this.telemetryHost = telemetryHost;
140        this.userEmail = userEmail;
141        this.password = password;
142        this.initializeMeasures();
143        this.loadUserConfiguration();
144        // List<ParameterDefinition> paramDefList =
145        // telemetrySession.getParameterList(measure.getName());
146      }
147    
148      /**
149       * @param startDate the start date.
150       * @param endDate the end date.
151       * @param selectedProjects the selected projects.
152       * @param granularity the granularity this data model focus.
153       */
154      public void setModel(long startDate, long endDate, List<Project> selectedProjects,
155          String granularity) {
156        this.startDate = startDate;
157        this.endDate = endDate;
158        this.granularity = granularity;
159        this.selectedProjects = selectedProjects;
160        for (PortfolioMeasureConfiguration measure : getEnabledMeasures()) {
161          checkMeasureParameters(measure);
162        }
163        measuresCharts.clear();
164      }
165    
166      /**
167       * Initialize the measure configurations.
168       */
169      private void initializeMeasures() {
170        // Load default measures
171        measures.clear();
172        
173        // Load additional user customized measures.
174        //PortfolioDefinitions portfolioDefinitions = getPortfolioDefinitions();
175        if (portfolioDefinitions != null) {
176          for (Measure measure : portfolioDefinitions.getMeasures().getMeasure()) {
177            measures.add(new PortfolioMeasureConfiguration(measure.getName(), measure.getAlias(), 
178                measure.getMerge(), measure.isEnabled(), getClassifier(measure), this));
179          }
180        }
181        for (PortfolioMeasureConfiguration measure : getEnabledMeasures()) {
182          checkMeasureParameters(measure);
183        }
184      }
185    
186      /**
187       * Get a {@link StreamClassifier} according to the given Measure.
188       * @param measure a given {@link Measure} object.
189       * @return a {@link StreamClassifier} instance, 
190       * null if no supported classifier defined in the Measure object.
191       */
192      protected static StreamClassifier getClassifier(Measure measure) {
193        if (measure == null) {
194          return null;
195        }
196        String classifierMethod = measure.getClassifierMethod();
197        if ("StreamTrend".equalsIgnoreCase(classifierMethod)) {
198          if (measure.getStreamTrendParameters() == null) {
199            return new StreamTrendClassifier(0, 0, true, false);
200          }
201          Double lowerThreshold = measure.getStreamTrendParameters().getLowerThresold();
202          if (lowerThreshold == null) {
203            lowerThreshold = 0.0;
204          }
205          Double higherThreshold = measure.getStreamTrendParameters().getHigherThresold();
206          if (higherThreshold == null) {
207            higherThreshold = 0.0;
208          }
209          Boolean higherBetter = true;
210          if (measure.getStreamTrendParameters().isSetHigherBetter()) {
211            higherBetter = measure.getStreamTrendParameters().isHigherBetter();
212          }
213          Boolean scaleWithGranularity = false;
214          if (measure.getStreamTrendParameters().isSetScaleWithGranularity()) {
215            scaleWithGranularity = measure.getStreamTrendParameters().isScaleWithGranularity();
216          }
217          return new StreamTrendClassifier(
218              lowerThreshold, higherThreshold, higherBetter, scaleWithGranularity);
219        }
220        else if ("Participation".equalsIgnoreCase(classifierMethod)) {
221          if (measure.getParticipationParameters() == null) {
222            return new StreamParticipationClassifier(50, 0, 50, true);
223          }
224          Double memberPercentage = measure.getParticipationParameters().getMemberPercentage();
225          if (memberPercentage == null) {
226            memberPercentage = 50.0;
227          }
228          Double valueThreshold = measure.getParticipationParameters().getThresoldValue();
229          if (valueThreshold == null) {
230            valueThreshold = 0.0;
231          }
232          Double frequencyPercentage = 
233            measure.getParticipationParameters().getFrequencyPercentage();
234          if (frequencyPercentage == null) {
235            frequencyPercentage = 50.0;
236          }
237          Boolean scaleWithGranularity = true;
238          if (measure.getParticipationParameters().isSetScaleWithGranularity()) {
239            scaleWithGranularity = measure.getParticipationParameters().isScaleWithGranularity();
240          }
241          return new StreamParticipationClassifier(
242              memberPercentage, valueThreshold, frequencyPercentage, scaleWithGranularity);
243        }
244        else if ("Deviation".equalsIgnoreCase(classifierMethod)) {
245          if (measure.getDeviationParameters() == null) {
246            return new StreamDeviationClassifier(40, 20, 50, true);
247          }
248          Double moderateDeviation = measure.getDeviationParameters().getModerateDeviation();
249          if (moderateDeviation == null) {
250            moderateDeviation = 40.0;
251          }
252          Double unacceptableDeviation = measure.getDeviationParameters().getUnacceptableDeviation();
253          if (unacceptableDeviation == null) {
254            unacceptableDeviation = 20.0;
255          }
256          Double expectationValue = measure.getDeviationParameters().getExpectationValue();
257          if (expectationValue == null) {
258            expectationValue = 50.0;
259          }
260          Boolean scaleWithGranularity = true;
261          if (measure.getDeviationParameters().isSetScaleWithGranularity()) {
262            scaleWithGranularity = measure.getDeviationParameters().isScaleWithGranularity();
263          }
264          return new StreamDeviationClassifier(
265              moderateDeviation, unacceptableDeviation, expectationValue, scaleWithGranularity);
266        }
267        return null;
268      }
269      
270      /**
271       * Print the {@link Measure} for logging purpose.
272       * 
273       * @param measure the {@link Measure} to log.
274       * @return the string
275       */
276      private String printPortfolioMeasure(Measure measure) {
277        String s = "/";
278        String classifierParameters = "";
279        String classifier = measure.getClassifierMethod();
280        if (StreamTrendClassifier.name.equalsIgnoreCase(classifier) && 
281            measure.getStreamTrendParameters() != null) {
282          classifierParameters = measure.getStreamTrendParameters().getLowerThresold() + s +
283                                 measure.getStreamTrendParameters().getHigherThresold() + s + 
284                                 measure.getStreamTrendParameters().isHigherBetter();
285        }
286        else if (StreamParticipationClassifier.name.equals(classifier) && 
287            measure.getParticipationParameters() != null) {
288          classifierParameters = measure.getParticipationParameters().getMemberPercentage() + s +
289                                 measure.getParticipationParameters().getThresoldValue() + s +
290                                 measure.getParticipationParameters().getFrequencyPercentage();
291        }
292        String enabled = "disabled";
293        if (measure.isEnabled()) {
294          enabled = "enabled";
295        }
296        return "<" + measure.getName() + ": " + s + enabled + s
297            + classifier + s + classifierParameters + s + 
298            measure.getTelemetryParameters() + "> ";
299      }
300    
301      /**
302       * Save user's configuration to system's cache.
303       * 
304       * @return change maked in this saving.
305       */
306      public String saveUserConfiguration() {
307        StringBuffer log = new StringBuffer();
308        UriCache userCache = this.getUserConfiguartionCache();
309        for (PortfolioMeasureConfiguration measure : this.measures) {
310          String uri = userEmail + "/portfolio/" + measure.getName();
311          Measure oldMeasure = null;
312          if (userCache.get(uri) instanceof Measure) {
313            oldMeasure = (Measure) userCache.get(uri);
314          }
315          else {
316            userCache.remove(uri);
317          }
318          Measure newMeasure = this.getMeasure(measure);
319          if (oldMeasure != null) {
320            String oldMeasureString = printPortfolioMeasure(oldMeasure);
321            String newMeasureString = printPortfolioMeasure(newMeasure);
322            if (!newMeasureString.equals(oldMeasureString)) {
323              mergeMeasures(newMeasure, oldMeasure);
324              userCache.put(uri, newMeasure);
325              log.append(newMeasureString);
326            }
327          }
328        }
329    
330        if (log.length() > 0) {
331          ProjectBrowserSession.get().logUsage("PORTFOLIO CONFIGURATION: {changed} " + log.toString());
332        }
333        return log.toString();
334      }
335    
336      /**
337       * Merge the old measure into the new one.
338       * @param newMeasure The new Measure.
339       * @param oldMeasure The old Measure.
340       */
341      private void mergeMeasures(Measure newMeasure, Measure oldMeasure) {
342        if (oldMeasure == null) {
343          return;
344        }
345        if (newMeasure.getStreamTrendParameters() == null) {
346          newMeasure.setStreamTrendParameters(oldMeasure.getStreamTrendParameters());
347        }
348        if (newMeasure.getParticipationParameters() == null) {
349          newMeasure.setParticipationParameters(oldMeasure.getParticipationParameters());
350        }
351      }
352    
353      /**
354       * Return a {@link Measure} represents the given PortfolioMeasureConfiguration.
355       * @param measureConfiguration a {@link PortfolioMeasureConfiguration}.
356       * @return a {@link Measure}.
357       */
358      private Measure getMeasure(PortfolioMeasureConfiguration measureConfiguration) {
359        Measure measure = new Measure();
360        measure.setName(measureConfiguration.getName());
361        measure.setEnabled(measureConfiguration.isEnabled());
362        measure.setTelemetryParameters(measureConfiguration.getParamtersString());
363        measure.setClassifierMethod(measureConfiguration.getClassiferName());
364        measureConfiguration.saveClassifierSetting(measure);
365        
366        return measure;
367      }
368      
369      /**
370       * Load user's configuration from system's cache.
371       */
372      private void loadUserConfiguration() {
373        for (PortfolioMeasureConfiguration measure : this.measures) {
374          Measure savedMeausre = getSavedMeasure(measure);
375          if (savedMeausre != null) {
376            measure.setMeasureConfiguration(savedMeausre.isEnabled(), getClassifier(savedMeausre), 
377                                            savedMeausre.getTelemetryParameters());
378          }
379        }
380      }
381    
382      /**
383       * Get the {@link Measure} instance associated with the given 
384       * {@link PortfolioMeasureConfiguration} from UriCache.
385       * @param measure the given {@link PortfolioMeasureConfiguration}
386       * @return the {@link Measure} instance, null if not matched found in cache.
387       */
388      protected final Measure getSavedMeasure(PortfolioMeasureConfiguration measure) {
389        UriCache userCache = this.getUserConfiguartionCache();
390        String uri = userEmail + "/portfolio/" + measure.getName();
391        Measure savedMeausre = null;
392        if (userCache.get(uri) instanceof Measure) {
393          savedMeausre = (Measure) userCache.get(uri);
394        }
395        else {
396          userCache.remove(uri);
397        }
398        return savedMeausre;
399      }
400      
401      /**
402       * Reset user's configuration cache.
403       */
404      public void resetUserConfiguration() {
405        // UriCache userCache = this.getUserConfiguartionCache();
406        // userCache.clearAll();
407        this.initializeMeasures();
408        String msg = this.saveUserConfiguration();
409        if (msg.length() > 0) {
410          ProjectBrowserSession.get().logUsage("CONFIGURATION: {reset to default} ");
411        }
412      }
413    
414      /**
415       * Load the portfolio definitions from the xml file.
416       * 
417       * @return a PortfolioDefinitions instance. null if fail to load the file.
418       */
419      private PortfolioDefinitions getPortfolioDefinitions() {
420        PortfolioDefinitions portfolioDefinitions = new PortfolioDefinitions();
421        portfolioDefinitions.setMeasures(new Measures());
422        // load the basic default definitions.
423        
424        getLogger().info("Reading basic portfolio definitions from: basic.portfolio.definition.xml");
425        InputStream defStream = ProjectPortfolioDataModel.class
426            .getResourceAsStream("basic.portfolio.definition.xml");
427        PortfolioDefinitions definition = getDefinitions(defStream);
428        if (definition != null) {
429          portfolioDefinitions.getMeasures().getMeasure().addAll(definition.getMeasures().getMeasure());
430        }
431            
432    
433        // load user defined definitions.
434        String defDirString = ((ProjectBrowserApplication) ProjectBrowserApplication.get())
435            .getPortfolioDefinitionDir();
436        if (defDirString != null && defDirString.length() > 0) {
437          File defDir = new File(defDirString);
438          File[] xmlFiles = defDir.listFiles(new ExtensionFileFilter(".xml"));
439          if (xmlFiles.length > 0) {
440            getLogger().info("Reading portfolio definitions from: " + defDirString);
441            List<TelemetryChartRef> chartRefs = null;
442            try {
443              TelemetryClient telemetryClient = new TelemetryClient(telemetryHost, userEmail, password);
444              chartRefs = telemetryClient.getChartIndex().getTelemetryChartRef();
445            }
446            catch (TelemetryClientException e1) {
447              getLogger().warning("Error retrieving telemetry chart definitions." + e1.getMessage());
448            }
449            catch (Exception e) {
450              getLogger().warning("Unexpected error occurs. " + e.getMessage());
451            }
452            for (File xmlFile : xmlFiles) {
453              try {
454                getLogger().info("Reading portfolio definitions from: " + xmlFile);
455                PortfolioDefinitions def = getDefinitions(new FileInputStream(xmlFile));
456                if (def != null) {
457                  List<Measure> measureList = def.getMeasures().getMeasure();
458                  if (chartRefs != null) {
459                    List<Measure> invalidMeasures = new ArrayList<Measure>();
460                    for (Measure measure : measureList) {
461                      if (!chartRefs.contains(measure.getName())) {
462                        invalidMeasures.add(measure);
463                        getLogger().warning("Found invalid measure:" + measure.getName() + 
464                            ". Please double check with the Telemetry Chart definitions.");
465                      }
466                    }
467                    measureList.removeAll(invalidMeasures);
468                  }
469                  portfolioDefinitions.getMeasures().getMeasure().addAll(measureList);
470                }
471              }
472              catch (FileNotFoundException e) {
473                getLogger().info("Error reading definitions from: " + xmlFile + " " + e);
474              }
475            }
476          }
477          else {
478            getLogger().info("No portfolio definitions found in: " + defDirString);
479          }
480        }
481        else {
482          getLogger().info(ProjectBrowserProperties.PORTFOLIO_DEFINITION_DIR + " not defined.");
483        }
484        return portfolioDefinitions;
485      }
486    
487      /**
488       * Returns a TelemetryDefinitions object constructed from defStream, or null if unsuccessful.
489       * 
490       * @param defStream The input stream containing a TelemetryDefinitions object in XML format.
491       * @return The TelemetryDefinitions object.
492       */
493      private PortfolioDefinitions getDefinitions(InputStream defStream) {
494        // Read in the definitions file.
495        try {
496          JAXBContext jaxbContext = JAXBContext
497              .newInstance(org.hackystat.projectbrowser.page.projectportfolio.jaxb.ObjectFactory.class);
498          Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
499          // add schema checking
500          SchemaFactory schemaFactory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
501    
502          InputStream schemaStream = ProjectPortfolioDataModel.class
503              .getResourceAsStream("portfolioDefinitions.xsd");
504          StreamSource schemaSources = new StreamSource(schemaStream);
505          Schema schema = schemaFactory.newSchema(schemaSources);
506          unmarshaller.setSchema(schema);
507          return (PortfolioDefinitions) unmarshaller.unmarshal(defStream);
508        }
509        catch (JAXBException e1) {
510          Logger logger = getLogger();
511          logger.severe("Error occurs when parsing portfolio definition xml file with jaxb > "
512              + e1.getErrorCode() + ":" + e1.getMessage());
513          e1.printStackTrace();
514        }
515        catch (SAXException e) {
516          Logger logger = getLogger();
517          logger.warning("Error occurs when loading schema file: " + defStream.toString() + " > "
518              + e.getMessage());
519        }
520        return null;
521      }
522    
523      /**
524       * Load data from Hackystat service.
525       */
526      public void loadData() {
527    
528        this.inProcess = true;
529        this.complete = false;
530        this.processingMessage = "Retrieving data from Hackystat Telemetry service.\n";
531        try {
532          TelemetryClient telemetryClient = new TelemetryClient(telemetryHost, userEmail, password);
533          // prepare start and end time.
534          XMLGregorianCalendar startTime = getStartTimestamp();
535          XMLGregorianCalendar endTime = getEndTimestamp();
536          for (int i = 0; i < this.selectedProjects.size() && inProcess; i++) {
537            // prepare project's information
538            Project project = this.selectedProjects.get(i);
539            String owner = project.getOwner();
540            String projectName = project.getName();
541    
542            this.processingMessage += "Retrieve data for project " + projectName + " (" + (i + 1)
543                + " of " + this.selectedProjects.size() + ").\n";
544    
545            // get charts of this project
546            List<MiniBarChart> charts = new ArrayList<MiniBarChart>();
547    
548            List<PortfolioMeasureConfiguration> enableMeasures = getEnabledMeasures();
549    
550            for (int j = 0; j < enableMeasures.size(); ++j) {
551              PortfolioMeasureConfiguration measure = enableMeasures.get(j);
552    
553              this.processingMessage += "---> Retrieve " + measure.getName() + "<"
554                  + measure.getParamtersString() + ">" + " (" + (i + 1) + " .. " + (j + 1) + " of "
555                  + enableMeasures.size() + ").\n";
556              // get data from hackystat
557              if (!ProjectUtils.isValidStartTime(project, startTime)) {
558                startTime = project.getStartTime();
559              }
560              String s = "/";
561              getLogger().finest(
562                  "retriving telemetry: " + measure.getName() + s + owner + s + projectName + s
563                      + granularity + s + startTime + s + endTime + s + measure.getParamtersString());
564              TelemetryChartData chartData = telemetryClient.getChart(measure.getName(), owner,
565                  projectName, granularity, startTime, endTime, measure.getParamtersString());
566              // Log warning when portfolio definition refers to multi-stream telemetry chart.
567              if (chartData.getTelemetryStream().size() > 1) {
568                String merge = measure.getMerge();
569                if ((merge == null || merge.length() <= 0) && i == 0) {
570                  Logger logger = getLogger();
571                  logger.warning("Telemetry chart:" + measure.getName() + 
572                      " contains multiple streams, but there is no merge method found in portfolio " +
573                      "defintion. Please check your portfolio and telemetry definitions");
574                }
575              }
576              MiniBarChart chart = 
577                new MiniBarChart(chartData.getTelemetryStream(), measure, this.granularity);
578              chart.setTelemetryPageParameters(this.getTelemetryPageParameters(measure, project));
579              //chart.setDpdPageParameters(this.getDpdPageParameters(
580              //measure, project, chart.getLastValidIndex()));
581              charts.add(chart);
582            }
583            this.measuresCharts.put(project, charts);
584          }
585        }
586        catch (TelemetryClientException e) {
587          String errorMessage = "Errors when retrieving telemetry data: " + e.getMessage() + ". "
588              + "Please try again.";
589          this.processingMessage += errorMessage + "\n";
590    
591          this.complete = false;
592          this.inProcess = false;
593          return;
594        }
595        this.complete = inProcess;
596        if (this.complete) {
597          this.processingMessage += "All done.\n";
598        }
599        this.inProcess = false;
600      }
601    
602      /**
603       * Check the parameter in the given measure configuration. Set the parameter to default if it is
604       * incorrect.
605       * 
606       * @param measure the given MeasureConfiguration.
607       */
608      private void checkMeasureParameters(PortfolioMeasureConfiguration measure) {
609        List<ParameterDefinition> paramDefList = telemetrySession.getParameterList(measure.getName());
610        List<IModel> parameters = measure.getParameters();
611        //[1] check the size of parameters.
612        if (parameters.size() == paramDefList.size()) {
613          //[2] check data type compatible of each parameter.
614          for (int i = 0; i < parameters.size(); ++i) {
615            if (!TelemetrySession.isValueMatchType(
616                String.valueOf(measure.getParameters().get(i).getObject()), 
617                paramDefList.get(i).getType())) {
618              parameters.remove(i);
619              parameters.add(i, new Model(paramDefList.get(i).getType().getDefault()));
620              
621            }
622          }
623        }
624        //[1.5] rebuild the parameter if size is not match.
625        else {
626          parameters.clear();
627          for (ParameterDefinition paramDef : paramDefList) {
628            parameters.add(new Model(paramDef.getType().getDefault()));
629          }
630        }
631      }
632    
633      /**
634       * Cancel the data loading process.
635       */
636      public void cancelDataUpdate() {
637        this.processingMessage += "Process Cancelled.\n";
638        this.inProcess = false;
639      }
640    
641      /**
642       * Return a PageParameters instance that include necessary information for telemetry.
643       * 
644       * @param measure the telemetry analysis
645       * @param project the project
646       * @return the PagaParameters object
647       */
648      private PageParameters getTelemetryPageParameters(PortfolioMeasureConfiguration measure, 
649          Project project) {
650        PageParameters parameters = new PageParameters();
651    
652        parameters.put(TelemetrySession.TELEMETRY_KEY, measure.getName());
653        parameters.put(TelemetrySession.START_DATE_KEY, getStartTimestamp().toString());
654        parameters.put(TelemetrySession.END_DATE_KEY, getEndTimestamp().toString());
655        parameters.put(TelemetrySession.SELECTED_PROJECTS_KEY, 
656            ProjectBrowserSession.convertProjectListToString(Arrays.asList(new Project[]{project})));
657        parameters.put(TelemetrySession.GRANULARITY_KEY, this.granularity);
658        parameters.put(TelemetrySession.TELEMETRY_PARAMERTERS_KEY, measure.getParamtersString());
659    
660        return parameters;
661      }
662    
663      /**
664       * Return a PageParameters instance that include necessary information for telemetry.
665       * 
666       * @param measure the telemetry analysis
667       * @param project the project
668       * @param indexOfLastValue the index of last valid value.
669       * @return the PagaParameters object, null if indexOfLastValue is negative, 
670       * which means last value is not available.
671       */
672      /*
673      private PageParameters getDpdPageParameters(PortfolioMeasureConfiguration measure, 
674          Project project, int indexOfLastValue) {
675        if (indexOfLastValue < 0) {
676          return null;
677        }
678        PageParameters parameters = new PageParameters();
679        
680    
681        parameters.put(DailyProjectDataSession.ANALYSIS_KEY, measure.getName());
682        parameters.put(DailyProjectDataSession.DATE_KEY, );
683        parameters.put(DailyProjectDataSession.SELECTED_PROJECTS_KEY, 
684            ProjectBrowserSession.convertProjectListToString(Arrays.asList(new Project[]{project})));
685    
686        return parameters;
687      }
688      */
689      
690      /**
691       * Return the end time stamp for analysis. If includeCurrentWeek, it will return the time stamp of
692       * yesterday. If !includeCurrentWeek, it will return the time stamp of last Saturday.
693       * 
694       * @return the XMLGregorianCalendar instance.
695       */
696      private XMLGregorianCalendar getEndTimestamp() {
697        /*
698         * if (!this.includeCurrentWeek) { GregorianCalendar date = new GregorianCalendar();
699         * date.setTime(ProjectBrowserBasePage.getDateBefore(1));
700         * date.setFirstDayOfWeek(Calendar.MONDAY); int dayOfWeek = date.get(Calendar.DAY_OF_WEEK);
701         * XMLGregorianCalendar endTime = Tstamp.makeTimestamp(date.getTimeInMillis()); endTime =
702         * Tstamp.incrementDays(endTime, - dayOfWeek); return endTime; } return
703         * Tstamp.makeTimestamp(ProjectBrowserBasePage.getDateBefore(1).getTime());
704         */
705        return Tstamp.makeTimestamp(this.endDate);
706      }
707    
708      /**
709       * Return the start time stamp for analysis.
710       * 
711       * @return the XMLGregorianCalendar instance.
712       */
713      private XMLGregorianCalendar getStartTimestamp() {
714        /*
715         * int days; if ("Month".equals(this.telemetryGranularity)) { days = this.timePhrase * 30; }
716         * else if ("Week".equals(this.telemetryGranularity)) { days = this.timePhrase * 7; } else {
717         * days = this.timePhrase; } return
718         * Tstamp.makeTimestamp(ProjectBrowserBasePage.getDateBefore(days).getTime());
719         */
720        return Tstamp.makeTimestamp(this.startDate);
721      }
722    
723      /**
724       * @return the inProcess
725       */
726      public boolean isInProcess() {
727        return inProcess;
728      }
729    
730      /**
731       * @return the complete
732       */
733      public boolean isComplete() {
734        return complete;
735      }
736    
737      /**
738       * @return the processingMessage
739       */
740      public String getProcessingMessage() {
741        return processingMessage;
742      }
743    
744      /**
745       * @return the analysesCharts
746       */
747      public Map<Project, List<MiniBarChart>> getMeasuresCharts() {
748        return measuresCharts;
749      }
750    
751      /**
752       * @return the selectedProjects
753       */
754      public List<Project> getSelectedProjects() {
755        return selectedProjects;
756      }
757    
758      /**
759       * Returns true if this model does not contain any data.
760       * 
761       * @return True if no data.
762       */
763      public boolean isEmpty() {
764        return this.selectedProjects.isEmpty();
765      }
766    
767      /**
768       * Return the default parameters of the given measure.
769       * 
770       * @param measure the given measure
771       * @return the parameters in a String
772       */
773      /*
774       * public String getDefaultParametersString(String measure) { List<ParameterDefinition>
775       * paramDefList = getParameterDefinitions(measure); StringBuffer param = new StringBuffer(); for
776       * (int i = 0; i < paramDefList.size(); ++i) { ParameterDefinition paramDef = paramDefList.get(i);
777       * param.append(paramDef.getType().getDefault()); if (i < paramDefList.size() - 1) {
778       * param.append(','); } } return param.toString(); }
779       */
780    
781      /**
782       * @return the measures
783       */
784      public List<PortfolioMeasureConfiguration> getMeasures() {
785        return measures;
786      }
787    
788      /**
789       * @return the enabled measures
790       */
791      public final List<PortfolioMeasureConfiguration> getEnabledMeasures() {
792        List<PortfolioMeasureConfiguration> enableMeasures = 
793          new ArrayList<PortfolioMeasureConfiguration>();
794        for (PortfolioMeasureConfiguration measure : measures) {
795          if (measure.isEnabled()) {
796            enableMeasures.add(measure);
797          }
798        }
799        return enableMeasures;
800      }
801    
802      /**
803       * Return the names of enabled measures. If alias available for the measure, alias will be
804       * returned. Otherwise, name in measure's definition will be returned.
805       * 
806       * @return the names of the enabled measures.
807       */
808      public List<String> getEnabledMeasuresName() {
809        List<String> names = new ArrayList<String>();
810        for (PortfolioMeasureConfiguration measure : measures) {
811          if (measure.isEnabled()) {
812            names.add(measure.getDisplayName());
813          }
814        }
815        return names;
816      }
817    
818      /**
819       * @param backgroundColor the backgroundColor to set
820       */
821      /*
822      public void setBackgroundColor(String backgroundColor) {
823        this.backgroundColor = backgroundColor;
824      }
825      */
826    
827      /**
828       * @return the backgroundColor
829       */
830      public String getBackgroundColor() {
831        return backgroundColor;
832      }
833      
834      /**
835       * @return the goodColor
836       */
837      public String getGoodColor() {
838        return goodColor;
839      }
840    
841      /**
842       * @return the sosoColor
843       */
844      public String getAverageColor() {
845        return averageColor;
846      }
847    
848      /**
849       * @return the badColor
850       */
851      public String getPoorColor() {
852        return poorColor;
853      }
854    
855      /**
856       * @return the fontColor
857       */
858      public String getFontColor() {
859        return fontColor;
860      }
861    
862      /**
863       * @return the naColor
864       */
865      public String getNAColor() {
866        return naColor;
867      }
868    
869      /**
870       * @return the userConfiguartionCache
871       */
872      private UriCache getUserConfiguartionCache() {
873        return new UriCache(userEmail, "portfolio", maxLife, capacity);
874      }
875    
876      /**
877       * @return the logger that associated to this web application.
878       */
879      public static Logger getLogger() {
880        return HackystatLogger.getLogger("org.hackystat.projectbrowser", "projectbrowser");
881      }
882    
883      /**
884       * Sort the table according to the given measure index.
885       * @param i the index of the measure in enabled measures.
886       */
887      public void sortTable(final int i) {
888        Collections.sort(this.selectedProjects, new Comparator<Project>() {
889          public int compare(Project o1, Project o2) {
890            int result = (int) (measuresCharts.get(o2).get(i).getLatestValue() * 10 -
891                         measuresCharts.get(o1).get(i).getLatestValue() * 10);
892            return result;
893          }
894          
895        });
896      }
897    
898      /**
899       * Sort the table by the project's name.
900       */
901      public void sortProjectNames() {
902        Collections.sort(this.selectedProjects, new Comparator<Project>() {
903          public int compare(Project o1, Project o2) {
904            return o1.getName().compareTo(o2.getName());
905          }
906          
907        });
908      }
909      
910    }