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 }