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 }