001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2009 SonarSource SA
004     * mailto:contact AT sonarsource DOT com
005     *
006     * Sonar is free software; you can redistribute it and/or
007     * modify it under the terms of the GNU Lesser General Public
008     * License as published by the Free Software Foundation; either
009     * version 3 of the License, or (at your option) any later version.
010     *
011     * Sonar is distributed in the hope that it will be useful,
012     * but WITHOUT ANY WARRANTY; without even the implied warranty of
013     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014     * Lesser General Public License for more details.
015     *
016     * You should have received a copy of the GNU Lesser General Public
017     * License along with Sonar; if not, write to the Free Software
018     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
019     */
020    package org.sonar.api.database.model;
021    
022    import org.apache.commons.lang.builder.ToStringBuilder;
023    import org.hibernate.annotations.Cache;
024    import org.hibernate.annotations.CacheConcurrencyStrategy;
025    import org.sonar.api.database.BaseIdentifiable;
026    import org.sonar.api.database.DatabaseSession;
027    import org.sonar.api.measures.Measure;
028    import org.sonar.api.measures.Metric;
029    import org.sonar.api.measures.RuleMeasure;
030    import org.sonar.api.rules.Rule;
031    import org.sonar.api.rules.RulePriority;
032    
033    import java.util.ArrayList;
034    import java.util.Date;
035    import java.util.List;
036    import javax.persistence.*;
037    
038    /**
039     * This class is the Hibernate model to store a measure in the DB
040     */
041    @Entity
042    @Table(name = "project_measures")
043    public class MeasureModel extends BaseIdentifiable implements Cloneable {
044    
045      public static final int TEXT_VALUE_LENGTH = 96;
046    
047      @Column(name = "value", updatable = true, nullable = true, precision = 30, scale = 20)
048      private Double value = 0.0;
049    
050      @Column(name = "text_value", updatable = true, nullable = true, length = TEXT_VALUE_LENGTH)
051      private String textValue;
052    
053      @Column(name = "tendency", updatable = true, nullable = true)
054      private Integer tendency;
055    
056      @ManyToOne(fetch = FetchType.EAGER)
057      @JoinColumn(name = "metric_id")
058      @Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
059      private Metric metric;
060    
061      @Column(name = "snapshot_id", updatable = true, nullable = true)
062      private Integer snapshotId;
063    
064      @Column(name = "project_id", updatable = true, nullable = true)
065      private Integer projectId;
066    
067      @Column(name = "description", updatable = true, nullable = true, length = 4000)
068      private String description;
069    
070      @Temporal(TemporalType.TIMESTAMP)
071      @Column(name = "measure_date", updatable = true, nullable = true)
072      private Date measureDate;
073    
074      @ManyToOne(fetch = FetchType.LAZY)
075      @JoinColumn(name = "rule_id")
076      @Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
077      private Rule rule;
078    
079      @Column(name = "rules_category_id")
080      private Integer rulesCategoryId;
081    
082      @Column(name = "rule_priority", updatable = false, nullable = true)
083      @Enumerated(EnumType.ORDINAL)
084      private RulePriority rulePriority;
085    
086      @Column(name = "alert_status", updatable = true, nullable = true, length = 5)
087      private String alertStatus;
088    
089      @Column(name = "alert_text", updatable = true, nullable = true, length = 4000)
090      private String alertText;
091    
092      @Column(name = "diff_value_1", updatable = true, nullable = true)
093      private Double diffValue1;
094    
095      @Column(name = "diff_value_2", updatable = true, nullable = true)
096      private Double diffValue2;
097    
098      @Column(name = "diff_value_3", updatable = true, nullable = true)
099      private Double diffValue3;
100    
101      @Column(name = "url", updatable = true, nullable = true, length = 2000)
102      private String url;
103    
104      @OneToMany(mappedBy = "measure", fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE})
105      private List<MeasureData> measureData = new ArrayList<MeasureData>();
106    
107      /**
108       * Creates a measure based on a metric and a double value
109       */
110      public MeasureModel(Metric metric, Double val) {
111        if (val.isNaN() || val.isInfinite()) {
112          throw new IllegalArgumentException("Measure value is NaN. Metric=" + metric);
113        }
114        this.metric = metric;
115        this.value = val;
116      }
117    
118      /**
119       * Creates a measure based on a metric and an alert level
120       */
121      public MeasureModel(Metric metric, Metric.Level level) {
122        this.metric = metric;
123        if (level != null) {
124          this.textValue = level.toString();
125        }
126      }
127    
128      /**
129       * Creates a measure based on a metric and a string value
130       */
131      public MeasureModel(Metric metric, String val) {
132        this.metric = metric;
133        setData(val);
134      }
135    
136      /**
137       * Creates an empty measure
138       */
139      public MeasureModel() {
140      }
141    
142      /**
143       * @return the measure double value
144       */
145      public Double getValue() {
146        return value;
147      }
148    
149      /**
150       * @return the measure description
151       */
152      public String getDescription() {
153        return description;
154      }
155    
156      /**
157       * Sets the measure description
158       */
159      public void setDescription(String description) {
160        this.description = description;
161      }
162    
163      /**
164       * Sets the measure value
165       *
166       * @throws IllegalArgumentException in case value is not a valid double
167       */
168      public MeasureModel setValue(Double value) throws IllegalArgumentException {
169        if (value != null && (value.isNaN() || value.isInfinite())) {
170          throw new IllegalArgumentException();
171        }
172        this.value = value;
173        return this;
174      }
175    
176      /**
177       * @return the measure alert level
178       */
179      public Metric.Level getLevelValue() {
180        if (textValue != null) {
181          return Metric.Level.valueOf(textValue);
182        }
183        return null;
184      }
185    
186      /**
187       * Use getData() instead
188       */
189      public String getTextValue() {
190        return textValue;
191      }
192    
193      /**
194       * Use setData() instead
195       */
196      public void setTextValue(String textValue) {
197        this.textValue = textValue;
198      }
199    
200      /**
201       * @return the measure tendency
202       */
203      public Integer getTendency() {
204        return tendency;
205      }
206    
207      /**
208       * @return whether the measure is about rule
209       */
210      public boolean isRuleMeasure() {
211        return rule != null || rulePriority != null || rulesCategoryId != null;
212      }
213    
214      /**
215       * Sets the measure tendency
216       *
217       * @return the current object
218       */
219      public MeasureModel setTendency(Integer tendency) {
220        this.tendency = tendency;
221        return this;
222      }
223    
224      /**
225       * @return the measure metric
226       */
227      public Metric getMetric() {
228        return metric;
229      }
230    
231      /**
232       * Sets the measure metric
233       */
234      public void setMetric(Metric metric) {
235        this.metric = metric;
236      }
237    
238      /**
239       * @return the snapshot id the measure is attached to
240       */
241      public Integer getSnapshotId() {
242        return snapshotId;
243      }
244    
245      /**
246       * Sets the snapshot id
247       *
248       * @return the current object
249       */
250      public MeasureModel setSnapshotId(Integer snapshotId) {
251        this.snapshotId = snapshotId;
252        return this;
253      }
254    
255      /**
256       * @return the rule
257       */
258      public Rule getRule() {
259        return rule;
260      }
261    
262      /**
263       * Sets the rule for the measure
264       *
265       * @return the current object
266       */
267      public MeasureModel setRule(Rule rule) {
268        this.rule = rule;
269        return this;
270      }
271    
272      /**
273       * @return the rule category id
274       */
275      public Integer getRulesCategoryId() {
276        return rulesCategoryId;
277      }
278    
279      /**
280       * Sets the rule category id
281       *
282       * @return the current object
283       */
284      public MeasureModel setRulesCategoryId(Integer id) {
285        this.rulesCategoryId = id;
286        return this;
287      }
288    
289      /**
290       * @return the rule priority
291       */
292      public RulePriority getRulePriority() {
293        return rulePriority;
294      }
295    
296      /**
297       * Sets the rule priority
298       */
299      public void setRulePriority(RulePriority rulePriority) {
300        this.rulePriority = rulePriority;
301      }
302    
303      /**
304       * @return the project id
305       */
306      public Integer getProjectId() {
307        return projectId;
308      }
309    
310      /**
311       * Sets the project id
312       */
313      public void setProjectId(Integer projectId) {
314        this.projectId = projectId;
315      }
316    
317      /**
318       * @return the date of the measure
319       */
320      public Date getMeasureDate() {
321        return measureDate;
322      }
323    
324      /**
325       * Sets the date for the measure
326       *
327       * @return the current object
328       */
329      public MeasureModel setMeasureDate(Date measureDate) {
330        this.measureDate = measureDate;
331        return this;
332      }
333    
334      /**
335       * @return the alert status if there is one, null otherwise
336       */
337      public Metric.Level getAlertStatus() {
338        if (alertStatus == null) {
339          return null;
340        }
341        return Metric.Level.valueOf(alertStatus);
342      }
343    
344      /**
345       * Sets the measure alert status
346       *
347       * @return the current object
348       */
349      public MeasureModel setAlertStatus(Metric.Level level) {
350        if (level != null) {
351          this.alertStatus = level.toString();
352        } else {
353          this.alertStatus = null;
354        }
355        return this;
356      }
357    
358      /**
359       * @return the measure data
360       */
361      public String getData() {
362        if (this.textValue != null) {
363          return this.textValue;
364        }
365        if (metric.isDataType() && !measureData.isEmpty()) {
366          return measureData.get(0).getText();
367        }
368        return null;
369      }
370    
371      /**
372       * Sets the measure data
373       */
374      public final void setData(String data) {
375        if (data == null) {
376          this.textValue = null;
377          measureData.clear();
378    
379        } else {
380          if (data.length() > TEXT_VALUE_LENGTH) {
381            measureData.clear();
382            measureData.add(new MeasureData(this, data));
383    
384          } else {
385            this.textValue = data;
386          }
387        }
388      }
389    
390      /**
391       * Use getData() instead
392       */
393      public MeasureData getMeasureData() {
394        if (!measureData.isEmpty()) {
395          return measureData.get(0);
396        }
397        return null;
398      }
399    
400      /**
401       * Use setData() instead
402       */
403      //@Deprecated
404      public void setMeasureData(MeasureData data) {
405        measureData.clear();
406        if (data != null) {
407          this.measureData.add(data);
408        }
409      }
410    
411      /**
412       * @return the text of the alert
413       */
414      public String getAlertText() {
415        return alertText;
416      }
417    
418      /**
419       * Sets the text for the alert
420       */
421      public void setAlertText(String alertText) {
422        this.alertText = alertText;
423      }
424    
425      /**
426       * @return the measure URL
427       */
428      public String getUrl() {
429        return url;
430      }
431    
432      /**
433       * Sets the measure URL
434       */
435      public void setUrl(String url) {
436        this.url = url;
437      }
438    
439      @Override
440      public String toString() {
441        return new ToStringBuilder(this).
442            append("value", value).
443            append("metric", metric).
444            toString();
445      }
446    
447      /**
448       * @return the rule id of the measure
449       */
450      public Integer getRuleId() {
451        if (getRule() != null) {
452          return getRule().getId();
453        }
454        return null;
455      }
456    
457      /**
458       * @return diffValue1
459       */
460      public Double getDiffValue1() {
461        return diffValue1;
462      }
463    
464      /**
465       * Sets the diffValue1
466       */
467      public void setDiffValue1(Double diffValue1) {
468        this.diffValue1 = diffValue1;
469      }
470    
471      /**
472       * @return diffValue2
473       */
474      public Double getDiffValue2() {
475        return diffValue2;
476      }
477    
478      /**
479       * Sets the diffValue2
480       */
481      public void setDiffValue2(Double diffValue2) {
482        this.diffValue2 = diffValue2;
483      }
484    
485      /**
486       * @return diffValue3
487       */
488      public Double getDiffValue3() {
489        return diffValue3;
490      }
491    
492      /**
493       * Sets the diffValue3
494       */
495      public void setDiffValue3(Double diffValue3) {
496        this.diffValue3 = diffValue3;
497      }
498    
499      /**
500       * Saves the current object to database
501       *
502       * @return the current object
503       */
504      public MeasureModel save(DatabaseSession session) {
505        this.metric = session.reattach(Metric.class, metric.getId());
506        MeasureData data = getMeasureData();
507        setMeasureData(null);
508        session.save(this);
509    
510        if (data != null) {
511          data.setMeasure(session.getEntity(MeasureModel.class, getId()));
512          data.setSnapshotId(snapshotId);
513          session.save(data);
514          setMeasureData(data);
515        }
516        return this;
517      }
518    
519      @Override
520      public Object clone() {
521        MeasureModel clone = new MeasureModel();
522        clone.setMetric(getMetric());
523        clone.setDescription(getDescription());
524        clone.setTextValue(getTextValue());
525        clone.setAlertStatus(getAlertStatus());
526        clone.setAlertText(getAlertText());
527        clone.setTendency(getTendency());
528        clone.setDiffValue1(getDiffValue1());
529        clone.setDiffValue2(getDiffValue2());
530        clone.setDiffValue3(getDiffValue3());
531        clone.setValue(getValue());
532        clone.setRulesCategoryId(getRulesCategoryId());
533        clone.setRulePriority(getRulePriority());
534        clone.setRule(getRule());
535        clone.setSnapshotId(getSnapshotId());
536        clone.setMeasureDate(getMeasureDate());
537        clone.setUrl(getUrl());
538        return clone;
539      }
540    
541    /**
542       * True if other fields than 'value' are set.
543       */
544      public boolean hasOptionalData() {
545        return getAlertStatus()!=null ||
546            getAlertText()!=null ||
547            getDescription()!=null ||
548            getDiffValue1()!=null ||
549            getDiffValue2()!=null ||
550            getDiffValue3()!=null ||
551            getMeasureData()!=null ||
552            getTendency()!=null ||
553            getUrl()!=null;
554      }
555    
556    
557      /**
558       * Builds a MeasureModel from a Measure
559       */
560      public static MeasureModel build(Measure measure) {
561        return build(measure, new MeasureModel());
562      }
563    
564      /**
565       * Merges a Measure into a MeasureModel
566       */
567      public static MeasureModel build(Measure measure, MeasureModel merge) {
568        merge.setMetric(measure.getMetric());
569        merge.setDescription(measure.getDescription());
570        merge.setData(measure.getData());
571        merge.setAlertStatus(measure.getAlertStatus());
572        merge.setAlertText(measure.getAlertText());
573        merge.setTendency(measure.getTendency());
574        merge.setDiffValue1(measure.getDiffValue1());
575        merge.setDiffValue2(measure.getDiffValue2());
576        merge.setDiffValue3(measure.getDiffValue3());
577        merge.setUrl(measure.getUrl());
578        if (measure.getValue() != null) {
579          merge.setValue(measure.getValue().doubleValue());
580        } else {
581          merge.setValue(null);
582        }
583        if (measure instanceof RuleMeasure) {
584          RuleMeasure ruleMeasure = (RuleMeasure) measure;
585          merge.setRulesCategoryId(ruleMeasure.getRuleCategory());
586          merge.setRulePriority(ruleMeasure.getRulePriority());
587          merge.setRule(ruleMeasure.getRule());
588        }
589        return merge;
590      }
591    
592      /**
593       * @return a measure from the current object
594       */
595      public Measure toMeasure() {
596        Measure measure;
597        if (isRuleMeasure()) {
598          measure = new RuleMeasure(getMetric(), getRule(), getRulePriority(), getRulesCategoryId());
599        } else {
600          measure = new Measure(getMetric());
601        }
602        measure.setId(getId());
603        measure.setDescription(getDescription());
604        measure.setValue(getValue());
605        measure.setData(getData());
606        measure.setAlertStatus(getAlertStatus());
607        measure.setAlertText(getAlertText());
608        measure.setTendency(getTendency());
609        measure.setDiffValue1(getDiffValue1());
610        measure.setDiffValue2(getDiffValue2());
611        measure.setDiffValue3(getDiffValue3());
612        measure.setUrl(getUrl());
613        return measure;
614      }
615    
616      /**
617       * Transforms a list of MeasureModel into a list of Measure
618       *
619       * @return an empty list if models is null
620       */
621      public static List<Measure> toMeasures(List<MeasureModel> models) {
622        List<Measure> result = new ArrayList<Measure>();
623        for (MeasureModel model : models) {
624          if (model != null) {
625            result.add(model.toMeasure());
626          }
627        }
628        return result;
629      }
630    }