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 }