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