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 }