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 }