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