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 }