Browse Source

SONAR-3621 use JSON format between rails and java components

This is the first step before the refactoring of the tables FILTERS, FILTER_COLUMNS and CRITERIA
tags/3.3
Simon Brandhof 11 years ago
parent
commit
223d3f26c6
31 changed files with 700 additions and 1751 deletions
  1. 4
    0
      sonar-application/src/main/assembly/conf/logback.xml
  2. 7
    2
      sonar-core/pom.xml
  3. 34
    23
      sonar-core/src/main/java/org/sonar/core/measure/MeasureFilter.java
  4. 12
    26
      sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterCondition.java
  5. 30
    27
      sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterContext.java
  6. 132
    0
      sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterDecoder.java
  7. 74
    0
      sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterEngine.java
  8. 26
    20
      sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterExecutor.java
  9. 8
    4
      sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterSort.java
  10. 32
    30
      sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterSql.java
  11. 14
    16
      sonar-core/src/test/java/org/sonar/core/measure/MeasureFilterContextTest.java
  12. 121
    0
      sonar-core/src/test/java/org/sonar/core/measure/MeasureFilterDecoderTest.java
  13. 55
    0
      sonar-core/src/test/java/org/sonar/core/measure/MeasureFilterEngineTest.java
  14. 74
    59
      sonar-core/src/test/java/org/sonar/core/measure/MeasureFilterExecutorTest.java
  15. 4
    0
      sonar-server/src/dev/h2/conf/logback.xml
  16. 4
    0
      sonar-server/src/dev/mysql/conf/logback.xml
  17. 4
    0
      sonar-server/src/dev/postgresql/conf/logback.xml
  18. 0
    393
      sonar-server/src/main/java/org/sonar/server/filters/Filter.java
  19. 0
    250
      sonar-server/src/main/java/org/sonar/server/filters/FilterExecutor.java
  20. 0
    142
      sonar-server/src/main/java/org/sonar/server/filters/FilterResult.java
  21. 0
    76
      sonar-server/src/main/java/org/sonar/server/filters/MeasureCriterion.java
  22. 9
    19
      sonar-server/src/main/java/org/sonar/server/platform/Platform.java
  23. 12
    26
      sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
  24. 1
    1
      sonar-server/src/main/webapp/WEB-INF/app/models/filter.rb
  25. 5
    15
      sonar-server/src/main/webapp/WEB-INF/app/models/filter_context.rb
  26. 38
    61
      sonar-server/src/main/webapp/WEB-INF/app/models/filters.rb
  27. 0
    47
      sonar-server/src/test/java/org/sonar/server/filters/DateCriterionTest.java
  28. 0
    361
      sonar-server/src/test/java/org/sonar/server/filters/FilterExecutorTest.java
  29. 0
    46
      sonar-server/src/test/java/org/sonar/server/filters/FilterResultTest.java
  30. 0
    68
      sonar-server/src/test/resources/org/sonar/server/filters/FilterExecutorTest/measures.xml
  31. 0
    39
      sonar-server/src/test/resources/org/sonar/server/filters/FilterExecutorTest/shared.xml

+ 4
- 0
sonar-application/src/main/assembly/conf/logback.xml View File

@@ -81,6 +81,10 @@
<level value="WARN"/>
</logger>

<logger name="org.sonar.MEASURE_FILTER">
<level value="WARN"/>
</logger>

<root>
<level value="INFO"/>
<appender-ref ref="SONAR_FILE"/>

+ 7
- 2
sonar-core/pom.xml View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
@@ -64,7 +65,11 @@
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-classworlds</artifactId>
</dependency>

<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1.1</version>
</dependency>

<!-- logging -->
<dependency>

+ 34
- 23
sonar-core/src/main/java/org/sonar/core/measure/MeasureFilter.java View File

@@ -20,31 +20,31 @@
package org.sonar.core.measure;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.sonar.api.measures.Metric;

import javax.annotation.Nullable;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;

public class MeasureFilter {
// conditions on resources
private String baseResourceKey;
private boolean onBaseResourceChildren = false; // only if baseResourceKey is set
private Set<String> resourceScopes = Sets.newHashSet();
private Set<String> resourceQualifiers = Sets.newHashSet();
private Set<String> resourceLanguages = Sets.newHashSet();
private boolean onBaseResourceChildren = false; // only if getBaseResourceKey is set
private List<String> resourceScopes = Lists.newArrayList();
private List<String> resourceQualifiers = Lists.newArrayList();
private List<String> resourceLanguages = Lists.newArrayList();
private String resourceName;
private Date fromDate = null, toDate = null;
private boolean userFavourites = false;

// conditions on measures
private List<MeasureFilterValueCondition> measureConditions = Lists.newArrayList();
private List<MeasureFilterCondition> measureConditions = Lists.newArrayList();

// sort
private MeasureFilterSort sort = new MeasureFilterSort();

public String baseResourceKey() {
public String getBaseResourceKey() {
return baseResourceKey;
}

@@ -62,18 +62,28 @@ public class MeasureFilter {
return onBaseResourceChildren;
}

public MeasureFilter setResourceScopes(Set<String> resourceScopes) {
this.resourceScopes = resourceScopes;
public MeasureFilter setResourceScopes(@Nullable List<String> l) {
this.resourceScopes = (l != null ? l : Collections.<String>emptyList());
return this;
}

public MeasureFilter setResourceQualifiers(String... qualifiers) {
this.resourceQualifiers = Sets.newHashSet(qualifiers);
public MeasureFilter setResourceQualifiers(List<String> l) {
this.resourceQualifiers = (l != null ? l : Collections.<String>emptyList());
return this;
}

public MeasureFilter setResourceLanguages(String... languages) {
this.resourceLanguages = Sets.newHashSet(languages);
public MeasureFilter setResourceQualifiers(String... l) {
this.resourceQualifiers = Lists.newArrayList(l);
return this;
}

public MeasureFilter setResourceLanguages(List<String> l) {
this.resourceLanguages = (l != null ? l : Collections.<String>emptyList());
return this;
}

public MeasureFilter setResourceLanguages(String... l) {
this.resourceLanguages = Lists.newArrayList(l);
return this;
}

@@ -82,11 +92,11 @@ public class MeasureFilter {
return this;
}

public boolean userFavourites() {
public boolean isOnFavourites() {
return userFavourites;
}

public String resourceName() {
public String getResourceName() {
return resourceName;
}

@@ -95,7 +105,7 @@ public class MeasureFilter {
return this;
}

public MeasureFilter addCondition(MeasureFilterValueCondition condition) {
public MeasureFilter addCondition(MeasureFilterCondition condition) {
this.measureConditions.add(condition);
return this;
}
@@ -111,6 +121,7 @@ public class MeasureFilter {
}

public MeasureFilter setSortOnMetric(Metric m) {
this.sort.setField(MeasureFilterSort.Field.METRIC);
this.sort.setMetric(m);
return this;
}
@@ -130,27 +141,27 @@ public class MeasureFilter {
return this;
}

public Date fromDate() {
public Date getFromDate() {
return fromDate;
}

public Date toDate() {
public Date getToDate() {
return toDate;
}

public Set<String> resourceScopes() {
public List<String> getResourceScopes() {
return resourceScopes;
}

public Set<String> resourceQualifiers() {
public List<String> getResourceQualifiers() {
return resourceQualifiers;
}

public Set<String> resourceLanguages() {
public List<String> getResourceLanguages() {
return resourceLanguages;
}

public List<MeasureFilterValueCondition> measureConditions() {
public List<MeasureFilterCondition> getMeasureConditions() {
return measureConditions;
}


sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterValueCondition.java → sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterCondition.java View File

@@ -21,35 +21,21 @@ package org.sonar.core.measure;

import org.sonar.api.measures.Metric;

public class MeasureFilterValueCondition {

public static enum Operator {
GREATER(">"), GREATER_OR_EQUALS(">="), EQUALS("="), LESS("<"), LESS_OR_EQUALS("<=");

private String sql;

private Operator(String sql) {
this.sql = sql;
}

public String getSql() {
return sql;
}
}
public class MeasureFilterCondition {

private final Metric metric;
private final Operator operator;
private final float value;
private int period = -1;
private final String operator;
private final double value;
private Integer period = null;

public MeasureFilterValueCondition(Metric metric, Operator operator, float value) {
public MeasureFilterCondition(Metric metric, String operator, double value) {
this.metric = metric;
this.operator = operator;
this.value = value;
}

public MeasureFilterValueCondition setPeriod(int period) {
this.period = (period > 0 ? period : -1);
public MeasureFilterCondition setPeriod(Integer period) {
this.period = period;
return this;
}

@@ -57,7 +43,7 @@ public class MeasureFilterValueCondition {
return metric;
}

public Operator operator() {
public String operator() {
return operator;
}

@@ -65,20 +51,20 @@ public class MeasureFilterValueCondition {
return value;
}

public int period() {
public Integer period() {
return period;
}

String valueColumn() {
if (period > 0) {
if (period != null) {
return "pm.variation_value_" + period;
}
return "pm.value";
}

void appendSql(StringBuilder sql) {
void appendSqlCondition(StringBuilder sql) {
sql.append(" pm.metric_id=");
sql.append(metric.getId());
sql.append(" AND ").append(valueColumn()).append(operator.getSql()).append(value);
sql.append(" AND ").append(valueColumn()).append(operator).append(value);
}
}

sonar-server/src/main/java/org/sonar/server/filters/DateCriterion.java → sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterContext.java View File

@@ -17,57 +17,60 @@
* License along with Sonar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package org.sonar.server.filters;
package org.sonar.core.measure;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.commons.lang.time.DateUtils;
import org.sonar.core.resource.SnapshotDto;

import java.util.Calendar;
import java.util.Date;
class MeasureFilterContext {
private Long userId;
private SnapshotDto baseSnapshot;
private String sql;
private String json;

public class DateCriterion {

private String operator;
private Date date;

public DateCriterion(String operator, Date date) {
this.operator = operator;
this.date = date;
Long getUserId() {
return userId;
}

public DateCriterion() {
MeasureFilterContext setUserId(Long userId) {
this.userId = userId;
return this;
}

public String getOperator() {
return operator;
SnapshotDto getBaseSnapshot() {
return baseSnapshot;
}

public DateCriterion setOperator(String operator) {
this.operator = operator;
MeasureFilterContext setBaseSnapshot(SnapshotDto baseSnapshot) {
this.baseSnapshot = baseSnapshot;
return this;
}

public Date getDate() {
return date;
String getSql() {
return sql;
}

public DateCriterion setDate(Date date) {
this.date = date;
MeasureFilterContext setSql(String sql) {
this.sql = sql;
return this;
}

public DateCriterion setDate(int daysAgo) {
this.date = DateUtils.addDays(new Date(), -daysAgo);
this.date = DateUtils.truncate(this.date, Calendar.DATE);
String getJson() {
return json;
}

MeasureFilterContext setJson(String json) {
this.json = json;
return this;
}

@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
.append("operator", operator)
.append("date", date)
.toString();
.append("json", json)
.append("sql", sql)
.append("user", userId)
.toString();
}
}

+ 132
- 0
sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterDecoder.java View File

@@ -0,0 +1,132 @@
/*
* Sonar, open source software quality management tool.
* Copyright (C) 2008-2012 SonarSource
* mailto:contact AT sonarsource DOT com
*
* Sonar is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* Sonar is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with Sonar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package org.sonar.core.measure;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.sonar.api.ServerComponent;
import org.sonar.api.measures.Metric;
import org.sonar.api.measures.MetricFinder;
import org.sonar.api.utils.DateUtils;

import java.util.Calendar;
import java.util.Date;
import java.util.List;

public class MeasureFilterDecoder implements ServerComponent {
private MetricFinder metricFinder;
private JSONParser parser = new JSONParser();

public MeasureFilterDecoder(MetricFinder metricFinder) {
this.metricFinder = metricFinder;
}

public MeasureFilter decode(String text) throws ParseException {
MeasureFilter filter = new MeasureFilter();
JSONObject map = (JSONObject) parser.parse(text);
parseResourceConditions(filter, map);
parseMeasureConditions(map, filter);
parseSorting(map, filter);
return filter;
}

private void parseResourceConditions(MeasureFilter filter, JSONObject map) {
filter.setBaseResourceKey((String) map.get("base"));
if (map.containsKey("onBaseChildren")) {
filter.setOnBaseResourceChildren(((Boolean) map.get("onBaseChildren")).booleanValue());
}
filter.setResourceScopes((List<String>) map.get("scopes"));
filter.setResourceQualifiers((List<String>) map.get("qualifiers"));
filter.setResourceLanguages((List<String>) map.get("languages"));
filter.setResourceName((String) map.get("name"));

if (map.containsKey("fromDate")) {
filter.setFromDate(toDate(map, "fromDate"));
}
if (map.containsKey("afterDays")) {
filter.setFromDate(toDays(map, "afterDays"));
}
if (map.containsKey("toDate")) {
filter.setToDate(toDate(map, "toDate"));
}
if (map.containsKey("beforeDays")) {
filter.setToDate(toDays(map, "beforeDays"));
}
if (map.containsKey("favourites")) {
filter.setUserFavourites(((Boolean) map.get("favourites")).booleanValue());
}
}

private void parseSorting(JSONObject map, MeasureFilter filter) {
if (map.containsKey("sortAsc")) {
filter.setSortAsc(((Boolean) map.get("sortAsc")).booleanValue());
}
String s = (String) map.get("sortField");
if (s != null) {
filter.setSortOn(MeasureFilterSort.Field.valueOf(s));
}
s = (String) map.get("sortField");
if (s != null) {
filter.setSortOn(MeasureFilterSort.Field.valueOf((String) map.get("sortField")));
}
s = (String) map.get("sortMetric");
if (s != null) {
filter.setSortOnMetric(metricFinder.findByKey(s));
}
if (map.containsKey("sortPeriod")) {
filter.setSortOnPeriod(((Long) map.get("sortPeriod")).intValue());
}
}

private void parseMeasureConditions(JSONObject map, MeasureFilter filter) {
JSONArray conditions = (JSONArray) map.get("conditions");
if (conditions != null) {
for (Object obj : conditions) {
JSONObject c = (JSONObject) obj;
Metric metric = metricFinder.findByKey((String) c.get("metric"));
String operator = (String) c.get("op");
Double value = (Double) c.get("val");
MeasureFilterCondition condition = new MeasureFilterCondition(metric, operator, value);
if (c.containsKey("period")) {
condition.setPeriod(((Long) c.get("period")).intValue());
}
filter.addCondition(condition);
}
}
}

private static Date toDate(JSONObject map, String key) {
String date = (String) map.get(key);
if (date != null) {
return DateUtils.parseDate(date);
}
return null;
}

private static Date toDays(JSONObject map, String key) {
int days = ((Long) map.get(key)).intValue();
Date date = org.apache.commons.lang.time.DateUtils.truncate(new Date(), Calendar.DATE);
date = org.apache.commons.lang.time.DateUtils.addDays(date, -days);
return date;
}

}

+ 74
- 0
sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterEngine.java View File

@@ -0,0 +1,74 @@
/*
* Sonar, open source software quality management tool.
* Copyright (C) 2008-2012 SonarSource
* mailto:contact AT sonarsource DOT com
*
* Sonar is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* Sonar is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with Sonar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package org.sonar.core.measure;

import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang.SystemUtils;
import org.json.simple.parser.ParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.ServerComponent;

import javax.annotation.Nullable;
import java.util.List;

public class MeasureFilterEngine implements ServerComponent {
private static final Logger FILTER_LOG = LoggerFactory.getLogger("org.sonar.MEASURE_FILTER");

private final MeasureFilterDecoder decoder;
private final MeasureFilterExecutor executor;

public MeasureFilterEngine(MeasureFilterDecoder decoder, MeasureFilterExecutor executor) {
this.decoder = decoder;
this.executor = executor;
}

public List<MeasureFilterRow> execute(String filterJson, @Nullable Long userId) throws ParseException {
return execute(filterJson, userId, FILTER_LOG);
}

@VisibleForTesting
List<MeasureFilterRow> execute(String filterJson, @Nullable Long userId, Logger logger) {
MeasureFilterContext context = new MeasureFilterContext();
context.setJson(filterJson);
context.setUserId(userId);
try {
long start = System.currentTimeMillis();
MeasureFilter filter = decoder.decode(filterJson);
List<MeasureFilterRow> rows = executor.execute(filter, context);
log(context, rows, (System.currentTimeMillis() - start), logger);
return rows;
} catch (Exception e) {
throw new IllegalStateException("Fail to execute filter: " + context, e);
}
}

private void log(MeasureFilterContext context, List<MeasureFilterRow> rows, long durationMs, Logger logger) {
if (logger.isDebugEnabled()) {
StringBuilder log = new StringBuilder();
log.append(SystemUtils.LINE_SEPARATOR);
log.append(" json: ").append(context.getJson()).append(SystemUtils.LINE_SEPARATOR);
log.append(" sql: ").append(context.getSql()).append(SystemUtils.LINE_SEPARATOR);
log.append("results: ").append(rows.size()).append(" rows in ").append(durationMs).append("ms").append(SystemUtils.LINE_SEPARATOR);
logger.debug(log.toString());
}
}

}

+ 26
- 20
sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterExecutor.java View File

@@ -20,20 +20,17 @@
package org.sonar.core.measure;

import org.apache.ibatis.session.SqlSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.ServerComponent;
import org.sonar.core.persistence.Database;
import org.sonar.core.persistence.MyBatis;
import org.sonar.core.resource.ResourceDao;

import javax.annotation.Nullable;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collections;
import java.util.List;

public class MeasureFilterExecutor implements ServerComponent {
private static final Logger FILTER_LOG = LoggerFactory.getLogger("org.sonar.MEASURE_FILTER");

private MyBatis mybatis;
private Database database;
@@ -45,24 +42,21 @@ public class MeasureFilterExecutor implements ServerComponent {
this.resourceDao = resourceDao;
}

public List<MeasureFilterRow> execute(MeasureFilter filter, @Nullable Long userId) {
public List<MeasureFilterRow> execute(MeasureFilter filter, MeasureFilterContext context) throws SQLException {
List<MeasureFilterRow> rows;
SqlSession session = null;
try {
session = mybatis.openSession();
MesasureFilterContext context = prepareContext(filter, userId, session);
prepareContext(context, filter, session);

if (isValid(filter, context)) {
MeasureFilterSql sql = new MeasureFilterSql(database, filter, context);
context.setSql(sql.sql());
Connection connection = session.getConnection();
rows = sql.execute(connection);
} else {
rows = Collections.emptyList();
}

} catch (Exception e) {
throw new IllegalStateException(e);

} finally {
MyBatis.closeQuietly(session);
}
@@ -70,19 +64,31 @@ public class MeasureFilterExecutor implements ServerComponent {
return rows;
}

private MesasureFilterContext prepareContext(MeasureFilter filter, Long userId, SqlSession session) {
MesasureFilterContext context = new MesasureFilterContext();
context.setUserId(userId);
if (filter.baseResourceKey() != null) {
context.setBaseSnapshot(resourceDao.getLastSnapshot(filter.baseResourceKey(), session));

private void prepareContext(MeasureFilterContext context, MeasureFilter filter, SqlSession session) {
if (filter.getBaseResourceKey() != null) {
context.setBaseSnapshot(resourceDao.getLastSnapshot(filter.getBaseResourceKey(), session));
}
return context;
}

static boolean isValid(MeasureFilter filter, MesasureFilterContext context) {
return
!(filter.resourceQualifiers().isEmpty() && !filter.userFavourites()) &&
static boolean isValid(MeasureFilter filter, MeasureFilterContext context) {
boolean valid =
!(filter.isOnBaseResourceChildren() && context.getBaseSnapshot() == null) &&
!(filter.userFavourites() && context.getUserId() == null);
!(filter.isOnFavourites() && context.getUserId() == null);
for (MeasureFilterCondition condition : filter.getMeasureConditions()) {
if (condition.period() != null && condition.period() < 1) {
valid = false;
}
if (condition.metric() == null) {
valid = false;
}
}
if (filter.sort().getPeriod() != null && filter.sort().getPeriod() < 1) {
valid = false;
}
if (filter.sort().onMeasures() && filter.sort().metric() == null) {
valid = false;
}
return valid;
}
}

+ 8
- 4
sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterSort.java View File

@@ -28,7 +28,7 @@ class MeasureFilterSort {

private Field field = Field.NAME;
private Metric metric = null;
private int period = -1;
private Integer period = null;
private boolean asc = true;

MeasureFilterSort() {
@@ -43,8 +43,12 @@ class MeasureFilterSort {
this.metric = metric;
}

void setPeriod(int period) {
this.period = (period > 0 ? period : -1);
Integer getPeriod() {
return period;
}

void setPeriod(Integer period) {
this.period = period;
}

void setAsc(boolean asc) {
@@ -92,7 +96,7 @@ class MeasureFilterSort {
break;
case METRIC:
if (metric.isNumericType()) {
column = (period > 0 ? "pm.variation_value_" + period : "pm.value");
column = (period != null ? "pm.variation_value_" + period : "pm.value");
} else {
column = "pm.text_value";
}

+ 32
- 30
sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterSql.java View File

@@ -39,11 +39,11 @@ class MeasureFilterSql {

private final Database database;
private final MeasureFilter filter;
private final MesasureFilterContext context;
private final MeasureFilterContext context;
private final StringBuilder sql = new StringBuilder(1000);
private final List<Date> dateParameters = Lists.newArrayList();

MeasureFilterSql(Database database, MeasureFilter filter, MesasureFilterContext context) {
MeasureFilterSql(Database database, MeasureFilter filter, MeasureFilterContext context) {
this.database = database;
this.filter = filter;
this.context = context;
@@ -71,22 +71,22 @@ class MeasureFilterSql {

private void init() {
sql.append("SELECT block.id, max(block.rid) rid, max(block.rootid) rootid, max(sortval) sortval");
for (int index = 0; index < filter.measureConditions().size(); index++) {
for (int index = 0; index < filter.getMeasureConditions().size(); index++) {
sql.append(", max(crit_").append(index).append(")");
}
sql.append(" FROM (");

appendSortBlock();
for (int index = 0; index < filter.measureConditions().size(); index++) {
MeasureFilterValueCondition condition = filter.measureConditions().get(index);
for (int index = 0; index < filter.getMeasureConditions().size(); index++) {
MeasureFilterCondition condition = filter.getMeasureConditions().get(index);
sql.append(" UNION ");
appendConditionBlock(index, condition);
}

sql.append(") block GROUP BY block.id");
if (!filter.measureConditions().isEmpty()) {
if (!filter.getMeasureConditions().isEmpty()) {
sql.append(" HAVING ");
for (int index = 0; index < filter.measureConditions().size(); index++) {
for (int index = 0; index < filter.getMeasureConditions().size(); index++) {
if (index > 0) {
sql.append(" AND ");
}
@@ -101,8 +101,8 @@ class MeasureFilterSql {

private void appendSortBlock() {
sql.append(" SELECT s.id, s.project_id rid, s.root_project_id rootid, ").append(filter.sort().column()).append(" sortval");
for (int index = 0; index < filter.measureConditions().size(); index++) {
MeasureFilterValueCondition condition = filter.measureConditions().get(index);
for (int index = 0; index < filter.getMeasureConditions().size(); index++) {
MeasureFilterCondition condition = filter.getMeasureConditions().get(index);
sql.append(", ").append(nullSelect(condition.metric())).append(" crit_").append(index);
}
sql.append(" FROM snapshots s INNER JOIN projects p ON s.project_id=p.id ");
@@ -115,14 +115,14 @@ class MeasureFilterSql {
appendResourceConditions();
}

private void appendConditionBlock(int conditionIndex, MeasureFilterValueCondition condition) {
private void appendConditionBlock(int conditionIndex, MeasureFilterCondition condition) {
sql.append(" SELECT s.id, s.project_id rid, s.root_project_id rootid, null sortval");
for (int j = 0; j < filter.measureConditions().size(); j++) {
for (int j = 0; j < filter.getMeasureConditions().size(); j++) {
sql.append(", ");
if (j == conditionIndex) {
sql.append(condition.valueColumn());
} else {
sql.append(nullSelect(filter.measureConditions().get(j).metric()));
sql.append(nullSelect(filter.getMeasureConditions().get(j).metric()));
}
sql.append(" crit_").append(j);
}
@@ -130,44 +130,46 @@ class MeasureFilterSql {
sql.append(" WHERE ");
appendResourceConditions();
sql.append(" AND pm.rule_id IS NULL AND pm.rule_priority IS NULL AND pm.characteristic_id IS NULL AND pm.person_id IS NULL AND ");
condition.appendSql(sql);
condition.appendSqlCondition(sql);
}

private void appendResourceConditions() {
sql.append(" s.status='P' AND s.islast=").append(database.getDialect().getTrueSqlValue());
sql.append(" AND p.copy_resource_id IS NULL ");
if (!filter.resourceQualifiers().isEmpty()) {
if (context.getBaseSnapshot() == null) {
sql.append(" AND p.copy_resource_id IS NULL ");
}
if (!filter.getResourceQualifiers().isEmpty()) {
sql.append(" AND s.qualifier IN ");
appendInStatement(filter.resourceQualifiers(), sql);
appendInStatement(filter.getResourceQualifiers(), sql);
}
if (!filter.resourceScopes().isEmpty()) {
if (!filter.getResourceScopes().isEmpty()) {
sql.append(" AND s.scope IN ");
appendInStatement(filter.resourceScopes(), sql);
appendInStatement(filter.getResourceScopes(), sql);
}
if (!filter.resourceLanguages().isEmpty()) {
if (!filter.getResourceLanguages().isEmpty()) {
sql.append(" AND p.language IN ");
appendInStatement(filter.resourceLanguages(), sql);
appendInStatement(filter.getResourceLanguages(), sql);
}
if (filter.fromDate() != null) {
if (filter.getFromDate() != null) {
sql.append(" AND s.created_at >= ? ");
dateParameters.add(new java.sql.Date(filter.fromDate().getTime()));
dateParameters.add(new java.sql.Date(filter.getFromDate().getTime()));
}
if (filter.toDate() != null) {
if (filter.getToDate() != null) {
sql.append(" AND s.created_at <= ? ");
dateParameters.add(new java.sql.Date(filter.toDate().getTime()));
dateParameters.add(new java.sql.Date(filter.getToDate().getTime()));
}
if (filter.userFavourites() && context.getUserId() != null) {
if (filter.isOnFavourites() && context.getUserId() != null) {
sql.append(" AND s.project_id IN (SELECT props.resource_id FROM properties props WHERE props.prop_key='favourite' AND props.user_id=");
sql.append(context.getUserId());
sql.append(" AND props.resource_id IS NOT NULL) ");
}
if (StringUtils.isNotBlank(filter.resourceName())) {
if (StringUtils.isNotBlank(filter.getResourceName())) {
sql.append(" AND s.project_id IN (SELECT rindex.resource_id FROM resource_index rindex WHERE rindex.kee like '");
sql.append(StringEscapeUtils.escapeSql(StringUtils.lowerCase(filter.resourceName())));
sql.append(StringEscapeUtils.escapeSql(StringUtils.lowerCase(filter.getResourceName())));
sql.append("%'");
if (!filter.resourceQualifiers().isEmpty()) {
if (!filter.getResourceQualifiers().isEmpty()) {
sql.append(" AND rindex.qualifier IN ");
appendInStatement(filter.resourceQualifiers(), sql);
appendInStatement(filter.getResourceQualifiers(), sql);
}
sql.append(") ");
}
@@ -178,7 +180,7 @@ class MeasureFilterSql {
} else {
Long rootSnapshotId = (baseSnapshot.getRootId() != null ? baseSnapshot.getRootId() : baseSnapshot.getId());
sql.append(" AND s.root_snapshot_id=").append(rootSnapshotId);
sql.append(" AND s.path LIKE '").append(baseSnapshot.getPath()).append(baseSnapshot.getId()).append(".%'");
sql.append(" AND s.path LIKE '").append(StringUtils.defaultString(baseSnapshot.getPath())).append(baseSnapshot.getId()).append(".%'");
}
}
}

sonar-core/src/main/java/org/sonar/core/measure/MesasureFilterContext.java → sonar-core/src/test/java/org/sonar/core/measure/MeasureFilterContextTest.java View File

@@ -19,25 +19,23 @@
*/
package org.sonar.core.measure;

import org.sonar.core.resource.SnapshotDto;
import org.junit.Test;

public class MesasureFilterContext {
private Long userId;
private SnapshotDto baseSnapshot;
import static org.fest.assertions.Assertions.assertThat;

public Long getUserId() {
return userId;
public class MeasureFilterContextTest {
@Test
public void test_empty_toString() {
MeasureFilterContext context = new MeasureFilterContext();
assertThat(context.toString()).isNotEmpty();
}

public void setUserId(Long userId) {
this.userId = userId;
}

public SnapshotDto getBaseSnapshot() {
return baseSnapshot;
}

public void setBaseSnapshot(SnapshotDto baseSnapshot) {
this.baseSnapshot = baseSnapshot;
@Test
public void test_toString() {
MeasureFilterContext context = new MeasureFilterContext();
context.setJson("{}");
context.setSql("SELECT *");
context.setUserId(50L);
assertThat(context.toString()).isEqualTo("MeasureFilterContext[json={},sql=SELECT *,user=50]");
}
}

+ 121
- 0
sonar-core/src/test/java/org/sonar/core/measure/MeasureFilterDecoderTest.java View File

@@ -0,0 +1,121 @@
/*
* Sonar, open source software quality management tool.
* Copyright (C) 2008-2012 SonarSource
* mailto:contact AT sonarsource DOT com
*
* Sonar is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* Sonar is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with Sonar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package org.sonar.core.measure;

import org.json.simple.parser.ParseException;
import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.sonar.api.measures.Metric;
import org.sonar.api.measures.MetricFinder;

import java.util.Date;

import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class MeasureFilterDecoderTest {

private MetricFinder metricFinder;

@Before
public void before() {
metricFinder = mock(MetricFinder.class);
when(metricFinder.findByKey(anyString())).thenAnswer(new Answer<Metric>() {
public Metric answer(InvocationOnMock invocationOnMock) throws Throwable {
return new Metric((String) invocationOnMock.getArguments()[0]);
}
});
}

@Test
public void should_decode() throws ParseException {
String json = "{\"base\": \"org.struts\", \"onBaseChildren\": true, \"scopes\": [\"PRJ\"], " +
"\"qualifiers\": [\"TRK\",\"CLA\"], " +
"\"languages\": [\"java\", \"php\"], \"name\": \"Struts\", \"fromDate\": \"2012-12-25\", " +
"\"toDate\": \"2013-01-31\", " +
"\"favourites\": true, " +
"\"sortAsc\": true, \"sortField\": \"METRIC\", \"sortMetric\": \"ncloc\", \"sortPeriod\":5, " +
"\"conditions\":[{\"metric\":\"lines\", \"op\":\">\", \"val\":123.0}]}";

MeasureFilter filter = new MeasureFilterDecoder(metricFinder).decode(json);

assertThat(filter.getBaseResourceKey()).isEqualTo("org.struts");
assertThat(filter.isOnBaseResourceChildren()).isTrue();
assertThat(filter.getResourceScopes()).containsExactly("PRJ");
assertThat(filter.getResourceQualifiers()).containsExactly("TRK", "CLA");
assertThat(filter.getResourceLanguages()).containsExactly("java", "php");
assertThat(filter.getResourceName()).isEqualTo("Struts");
assertThat(filter.getFromDate().getYear()).isEqualTo(2012 - 1900);
assertThat(filter.getToDate().getYear()).isEqualTo(2013 - 1900);
assertThat(filter.isOnFavourites()).isTrue();
assertThat(filter.sort().metric().getKey()).isEqualTo("ncloc");
assertThat(filter.sort().isAsc()).isTrue();
MeasureFilterCondition condition = filter.getMeasureConditions().get(0);
assertThat(condition.metric().getKey()).isEqualTo("lines");
assertThat(condition.operator()).isEqualTo(">");
assertThat(condition.value()).isEqualTo(123.0);
}

@Test
public void should_set_max_date_by_number_of_days() throws ParseException {
String json = "{\"beforeDays\": 5}";

MeasureFilter filter = new MeasureFilterDecoder(metricFinder).decode(json);

assertThat(filter.getFromDate()).isNull();
assertThat(filter.getToDate().before(new Date())).isTrue();
}

@Test
public void should_set_min_date_by_number_of_days() throws ParseException {
String json = "{\"afterDays\": 5}";

MeasureFilter filter = new MeasureFilterDecoder(metricFinder).decode(json);

assertThat(filter.getToDate()).isNull();
assertThat(filter.getFromDate().before(new Date())).isTrue();
}

@Test
public void test_default_values() throws ParseException {
MeasureFilter filter = new MeasureFilterDecoder(metricFinder).decode("{}");

assertThat(filter.getBaseResourceKey()).isNull();
assertThat(filter.isOnBaseResourceChildren()).isFalse();
assertThat(filter.getResourceScopes()).isEmpty();
assertThat(filter.getResourceQualifiers()).isEmpty();
assertThat(filter.getResourceLanguages()).isEmpty();
assertThat(filter.getResourceName()).isNull();
assertThat(filter.getFromDate()).isNull();
assertThat(filter.getToDate()).isNull();
assertThat(filter.isOnFavourites()).isFalse();
assertThat(filter.sort().metric()).isNull();
assertThat(filter.sort().getPeriod()).isNull();
assertThat(filter.sort().onMeasures()).isFalse();
assertThat(filter.sort().field()).isEqualTo(MeasureFilterSort.Field.NAME);
assertThat(filter.sort().isAsc()).isTrue();
assertThat(filter.getMeasureConditions()).isEmpty();

}
}

+ 55
- 0
sonar-core/src/test/java/org/sonar/core/measure/MeasureFilterEngineTest.java View File

@@ -0,0 +1,55 @@
/*
* Sonar, open source software quality management tool.
* Copyright (C) 2008-2012 SonarSource
* mailto:contact AT sonarsource DOT com
*
* Sonar is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* Sonar is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with Sonar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package org.sonar.core.measure;

import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.junit.Test;
import org.slf4j.Logger;

import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.*;

public class MeasureFilterEngineTest {
@Test
public void should_decode_json_and_execute_filter() throws Exception {
MeasureFilterDecoder decoder = mock(MeasureFilterDecoder.class);
MeasureFilter filter = new MeasureFilter();
when(decoder.decode("{}")).thenReturn(filter);
MeasureFilterExecutor executor = mock(MeasureFilterExecutor.class);
Logger logger = mock(Logger.class);
when(logger.isDebugEnabled()).thenReturn(true);

MeasureFilterEngine engine = new MeasureFilterEngine(decoder, executor);

final long userId = 50L;
engine.execute("{}", userId, logger);
verify(executor).execute(refEq(filter), argThat(new BaseMatcher<MeasureFilterContext>() {
public boolean matches(Object o) {
MeasureFilterContext context = (MeasureFilterContext) o;
return "{}".equals(context.getJson()) && context.getUserId() == userId;
}

public void describeTo(Description description) {
}
}));
verify(logger).debug(anyString());
}
}

+ 74
- 59
sonar-core/src/test/java/org/sonar/core/measure/MeasureFilterExecutorTest.java View File

@@ -27,6 +27,7 @@ import org.sonar.core.persistence.AbstractDaoTestCase;
import org.sonar.core.resource.ResourceDao;
import org.sonar.core.resource.SnapshotDto;

import java.sql.SQLException;
import java.util.List;

import static org.fest.assertions.Assertions.assertThat;
@@ -54,15 +55,15 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
}

@Test
public void invalid_filter_should_not_return_results() {
MeasureFilter filter = new MeasureFilter();
// no qualifiers
assertThat(executor.execute(filter, null)).isEmpty();
public void invalid_filter_should_not_return_results() throws SQLException {
MeasureFilter filter = new MeasureFilter().setUserFavourites(true);
// anonymous user does not have favourites
assertThat(executor.execute(filter, new MeasureFilterContext())).isEmpty();
}

@Test
public void filter_is_not_valid_if_missing_base_snapshot() {
MesasureFilterContext context = new MesasureFilterContext();
MeasureFilterContext context = new MeasureFilterContext();
MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setOnBaseResourceChildren(true);
assertThat(MeasureFilterExecutor.isValid(filter, context)).isFalse();

@@ -70,9 +71,23 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
assertThat(MeasureFilterExecutor.isValid(filter, context)).isTrue();
}

@Test
public void filter_is_not_valid_if_condition_on_unknown_metric() {
MeasureFilterContext context = new MeasureFilterContext();
MeasureFilter filter = new MeasureFilter().addCondition(new MeasureFilterCondition(null, "<", 3.0));
assertThat(MeasureFilterExecutor.isValid(filter, context)).isFalse();
}

@Test
public void filter_is_not_valid_if_sorting_on_unknown_metric() {
MeasureFilterContext context = new MeasureFilterContext();
MeasureFilter filter = new MeasureFilter().setSortOnMetric(null);
assertThat(MeasureFilterExecutor.isValid(filter, context)).isFalse();
}

@Test
public void filter_is_not_valid_if_anonymous_favourites() {
MesasureFilterContext context = new MesasureFilterContext();
MeasureFilterContext context = new MeasureFilterContext();
MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setUserFavourites(true);
assertThat(MeasureFilterExecutor.isValid(filter, context)).isFalse();

@@ -81,9 +96,9 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
}

@Test
public void projects_without_measure_conditions() {
public void projects_without_measure_conditions() throws SQLException {
MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setSortOn(MeasureFilterSort.Field.LANGUAGE);
List<MeasureFilterRow> rows = executor.execute(filter, null);
List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());

assertThat(rows).hasSize(2);
verifyJavaProject(rows.get(0));
@@ -100,9 +115,9 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
}

@Test
public void sort_by_ascending_resource_name() {
public void sort_by_ascending_resource_name() throws SQLException {
MeasureFilter filter = new MeasureFilter().setResourceQualifiers("CLA").setSortAsc(true);
List<MeasureFilterRow> rows = executor.execute(filter, null);
List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());

// Big -> Tiny
assertThat(rows).hasSize(2);
@@ -111,9 +126,9 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
}

@Test
public void sort_by_descending_resource_name() {
public void sort_by_descending_resource_name() throws SQLException {
MeasureFilter filter = new MeasureFilter().setResourceQualifiers("CLA").setSortAsc(false);
List<MeasureFilterRow> rows = executor.execute(filter, null);
List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());

// Tiny -> Big
assertThat(rows).hasSize(2);
@@ -122,9 +137,9 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
}

@Test
public void sort_by_ascending_text_measure() {
public void sort_by_ascending_text_measure() throws SQLException {
MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setSortOnMetric(METRIC_PROFILE);
List<MeasureFilterRow> rows = executor.execute(filter, null);
List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());

assertThat(rows).hasSize(2);
verifyPhpProject(rows.get(0));//php way
@@ -132,9 +147,9 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
}

@Test
public void sort_by_descending_text_measure() {
public void sort_by_descending_text_measure() throws SQLException {
MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setSortOnMetric(METRIC_PROFILE).setSortAsc(false);
List<MeasureFilterRow> rows = executor.execute(filter, null);
List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());

assertThat(rows).hasSize(2);
verifyJavaProject(rows.get(0));// Sonar way
@@ -142,18 +157,18 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
}

@Test
public void sort_by_missing_text_measure() {
public void sort_by_missing_text_measure() throws SQLException {
// the metric 'profile' is not set on files
MeasureFilter filter = new MeasureFilter().setResourceQualifiers("CLA").setSortOnMetric(METRIC_PROFILE);
List<MeasureFilterRow> rows = executor.execute(filter, null);
List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());

assertThat(rows).hasSize(2);//2 files randomly sorted
}

@Test
public void sort_by_ascending_numeric_measure() {
public void sort_by_ascending_numeric_measure() throws SQLException {
MeasureFilter filter = new MeasureFilter().setResourceQualifiers("CLA").setSortOnMetric(METRIC_LINES);
List<MeasureFilterRow> rows = executor.execute(filter, null);
List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());

// Tiny -> Big
assertThat(rows).hasSize(2);
@@ -162,9 +177,9 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
}

@Test
public void sort_by_descending_numeric_measure() {
public void sort_by_descending_numeric_measure() throws SQLException {
MeasureFilter filter = new MeasureFilter().setResourceQualifiers("CLA").setSortOnMetric(METRIC_LINES).setSortAsc(false);
List<MeasureFilterRow> rows = executor.execute(filter, null);
List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());

// Big -> Tiny
assertThat(rows).hasSize(2);
@@ -173,19 +188,19 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
}

@Test
public void sort_by_missing_numeric_measure() {
public void sort_by_missing_numeric_measure() throws SQLException {
// coverage measures are not computed
MeasureFilter filter = new MeasureFilter().setResourceQualifiers("CLA").setSortOnMetric(METRIC_COVERAGE);
List<MeasureFilterRow> rows = executor.execute(filter, null);
List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());

// 2 files, random order
assertThat(rows).hasSize(2);
}

@Test
public void sort_by_ascending_variation() {
public void sort_by_ascending_variation() throws SQLException {
MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setSortOnMetric(METRIC_LINES).setSortOnPeriod(5);
List<MeasureFilterRow> rows = executor.execute(filter, null);
List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());

assertThat(rows).hasSize(2);
verifyJavaProject(rows.get(0));// +400
@@ -193,10 +208,10 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
}

@Test
public void sort_by_descending_variation() {
public void sort_by_descending_variation() throws SQLException {
MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK")
.setSortOnMetric(METRIC_LINES).setSortOnPeriod(5).setSortAsc(false);
List<MeasureFilterRow> rows = executor.execute(filter, null);
List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());

assertThat(rows).hasSize(2);
verifyPhpProject(rows.get(0));// +4900
@@ -204,70 +219,70 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
}

@Test
public void sort_by_ascending_date() {
public void sort_by_ascending_date() throws SQLException {
MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setSortOn(MeasureFilterSort.Field.DATE);
List<MeasureFilterRow> rows = executor.execute(filter, null);
List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());

verifyJavaProject(rows.get(0));// 2008
verifyPhpProject(rows.get(1));// 2012
}

@Test
public void sort_by_descending_date() {
public void sort_by_descending_date() throws SQLException {
MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setSortOn(MeasureFilterSort.Field.DATE).setSortAsc(false);
List<MeasureFilterRow> rows = executor.execute(filter, null);
List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());

verifyPhpProject(rows.get(0));// 2012
verifyJavaProject(rows.get(1));// 2008
}

@Test
public void condition_on_numeric_measure() {
public void condition_on_numeric_measure() throws SQLException {
MeasureFilter filter = new MeasureFilter().setResourceQualifiers("CLA")
.setSortOnMetric(METRIC_LINES)
.addCondition(new MeasureFilterValueCondition(METRIC_LINES, MeasureFilterValueCondition.Operator.GREATER, 200));
List<MeasureFilterRow> rows = executor.execute(filter, null);
.addCondition(new MeasureFilterCondition(METRIC_LINES, ">", 200));
List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());

assertThat(rows).hasSize(1);
verifyJavaBigFile(rows.get(0));
}

@Test
public void condition_on_measure_variation() {
public void condition_on_measure_variation() throws SQLException {
MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK")
.setSortOnMetric(METRIC_LINES)
.addCondition(new MeasureFilterValueCondition(METRIC_LINES, MeasureFilterValueCondition.Operator.GREATER, 1000).setPeriod(5));
List<MeasureFilterRow> rows = executor.execute(filter, null);
.addCondition(new MeasureFilterCondition(METRIC_LINES, ">", 1000).setPeriod(5));
List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());

assertThat(rows).hasSize(1);
verifyPhpProject(rows.get(0));
}

@Test
public void multiple_conditions_on_numeric_measures() {
public void multiple_conditions_on_numeric_measures() throws SQLException {
MeasureFilter filter = new MeasureFilter().setResourceQualifiers("CLA")
.setSortOnMetric(METRIC_LINES)
.addCondition(new MeasureFilterValueCondition(METRIC_LINES, MeasureFilterValueCondition.Operator.GREATER, 2))
.addCondition(new MeasureFilterValueCondition(METRIC_LINES, MeasureFilterValueCondition.Operator.LESS_OR_EQUALS, 50));
List<MeasureFilterRow> rows = executor.execute(filter, null);
.addCondition(new MeasureFilterCondition(METRIC_LINES, ">", 2))
.addCondition(new MeasureFilterCondition(METRIC_LINES, "<=", 50));
List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());

assertThat(rows).hasSize(1);
verifyJavaTinyFile(rows.get(0));
}

@Test
public void filter_by_language() {
public void filter_by_language() throws SQLException {
MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setResourceLanguages("java", "cobol");
List<MeasureFilterRow> rows = executor.execute(filter, null);
List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());

assertThat(rows).hasSize(1);
verifyJavaProject(rows.get(0));
}

@Test
public void filter_by_min_date() {
public void filter_by_min_date() throws SQLException {
MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setFromDate(DateUtils.parseDate("2012-12-13"));
List<MeasureFilterRow> rows = executor.execute(filter, null);
List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());

// php has been analyzed in 2012-12-13, whereas java project has been analyzed in 2008
assertThat(rows).hasSize(1);
@@ -275,11 +290,11 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
}

@Test
public void filter_by_range_of_dates() {
public void filter_by_range_of_dates() throws SQLException {
MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK")
.setFromDate(DateUtils.parseDate("2007-01-01"))
.setToDate(DateUtils.parseDate("2010-01-01"));
List<MeasureFilterRow> rows = executor.execute(filter, null);
List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());

// php has been analyzed in 2012-12-13, whereas java project has been analyzed in 2008
assertThat(rows).hasSize(1);
@@ -287,18 +302,18 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
}

@Test
public void filter_by_resource_name() {
public void filter_by_resource_name() throws SQLException {
MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setResourceName("PHP Proj");
List<MeasureFilterRow> rows = executor.execute(filter, null);
List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());

assertThat(rows).hasSize(1);
verifyPhpProject(rows.get(0));
}

@Test
public void filter_by_base_resource() {
public void filter_by_base_resource() throws SQLException {
MeasureFilter filter = new MeasureFilter().setResourceQualifiers("CLA").setBaseResourceKey("java_project");
List<MeasureFilterRow> rows = executor.execute(filter, null);
List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());

assertThat(rows).hasSize(2);
// default sort is on resource name
@@ -307,26 +322,26 @@ public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
}

@Test
public void filter_by_parent_resource() {
MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK", "PAC", "CLA").setBaseResourceKey("java_project").setOnBaseResourceChildren(true);
List<MeasureFilterRow> rows = executor.execute(filter, null);
public void filter_by_parent_resource() throws SQLException {
MeasureFilter filter = new MeasureFilter().setBaseResourceKey("java_project").setOnBaseResourceChildren(true);
List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());

assertThat(rows).hasSize(1);// the package org.sonar.foo
assertThat(rows.get(0).getSnapshotId()).isEqualTo(JAVA_PACKAGE_SNAPSHOT_ID);
}

@Test
public void filter_by_parent_without_children() {
public void filter_by_parent_without_children() throws SQLException {
MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK", "PAC", "CLA").setBaseResourceKey("java_project:org.sonar.foo.Big").setOnBaseResourceChildren(true);
List<MeasureFilterRow> rows = executor.execute(filter, null);
List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext());

assertThat(rows).isEmpty();
}

@Test
public void filter_by_user_favourites() {
public void filter_by_user_favourites() throws SQLException {
MeasureFilter filter = new MeasureFilter().setUserFavourites(true);
List<MeasureFilterRow> rows = executor.execute(filter, 50L);
List<MeasureFilterRow> rows = executor.execute(filter, new MeasureFilterContext().setUserId(50L));

assertThat(rows).hasSize(2);
verifyJavaBigFile(rows.get(0));

+ 4
- 0
sonar-server/src/dev/h2/conf/logback.xml View File

@@ -31,6 +31,10 @@
<level value="DEBUG"/>
</logger>

<logger name="org.sonar.MEASURE_FILTER">
<level value="DEBUG"/>
</logger>

<root>
<level value="INFO"/>
<appender-ref ref="STDOUT"/>

+ 4
- 0
sonar-server/src/dev/mysql/conf/logback.xml View File

@@ -31,6 +31,10 @@
<level value="DEBUG"/>
</logger>

<logger name="org.sonar.MEASURE_FILTER">
<level value="DEBUG"/>
</logger>

<root>
<level value="INFO"/>
<appender-ref ref="STDOUT"/>

+ 4
- 0
sonar-server/src/dev/postgresql/conf/logback.xml View File

@@ -31,6 +31,10 @@
<level value="DEBUG"/>
</logger>

<logger name="org.sonar.MEASURE_FILTER">
<level value="DEBUG"/>
</logger>

<root>
<level value="INFO"/>
<appender-ref ref="STDOUT"/>

+ 0
- 393
sonar-server/src/main/java/org/sonar/server/filters/Filter.java View File

@@ -1,393 +0,0 @@
/*
* Sonar, open source software quality management tool.
* Copyright (C) 2008-2012 SonarSource
* mailto:contact AT sonarsource DOT com
*
* Sonar is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* Sonar is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with Sonar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package org.sonar.server.filters;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.sonar.api.resources.Qualifiers;

import java.util.List;
import java.util.Set;

public class Filter {

// path
private Integer rootSnapshotId;
private Integer baseSnapshotId;
private String baseSnapshotPath;

// filters on resources
private Set<String> scopes;
private Set<String> qualifiers;
private Set<String> languages;
private Set<Integer> favouriteIds;
private DateCriterion dateCriterion;
private String keyRegexp;
private String nameRegexp;
private boolean onDirectChildren = false;

// filters on measures
private List<MeasureCriterion> measureCriteria = Lists.newLinkedList();
private int periodIndex = 0;

// sorting
private Integer sortedMetricId;
private Boolean sortedByMeasureVariation = Boolean.FALSE;
private boolean sortedByLanguage;
private boolean sortedByName;
private boolean sortedByKey;
private boolean sortedByDate;
private boolean sortedByVersion;
private boolean isNumericMetric = true;
private boolean ascendingSort = true;

public Filter setPath(Integer rootSnapshotId, Integer snapshotId, String snapshotPath) {
this.baseSnapshotId = snapshotId;
if (rootSnapshotId == null) {
this.rootSnapshotId = snapshotId;
} else {
this.rootSnapshotId = rootSnapshotId;
}
this.baseSnapshotPath = StringUtils.defaultString(snapshotPath, ""); //With Oracle the path can be null (see SONAR-2582)
return this;
}

public Integer getRootSnapshotId() {
return rootSnapshotId;
}

public boolean hasBaseSnapshot() {
return baseSnapshotId != null;
}

public Integer getBaseSnapshotId() {
return baseSnapshotId;
}

public String getBaseSnapshotPath() {
return baseSnapshotPath;
}

public Set<String> getScopes() {
return scopes;
}

public boolean hasScopes() {
return scopes != null && !scopes.isEmpty();
}

public Filter setScopes(Set<String> scopes) {
this.scopes = scopes;
return this;
}

public Filter setScopes(String... scopes) {
this.scopes = Sets.newHashSet(scopes);
return this;
}

public Set<String> getQualifiers() {
return qualifiers;
}

public boolean hasQualifiers() {
return qualifiers != null && !qualifiers.isEmpty();
}

public Filter setQualifiers(Set<String> qualifiers) {
this.qualifiers = qualifiers;
return this;
}

public Filter setQualifiers(String... qualifiers) {
this.qualifiers = Sets.newHashSet(qualifiers);
return this;
}

public Set<String> getLanguages() {
return languages;
}

public boolean hasLanguages() {
return languages != null && !languages.isEmpty();
}

public Filter setLanguages(Set<String> languages) {
this.languages = languages;
return this;
}

public Filter setLanguages(String... languages) {
this.languages = Sets.newHashSet(languages);
return this;
}

public Set<Integer> getFavouriteIds() {
return favouriteIds;
}

public boolean hasFavouriteIds() {
return favouriteIds != null && !favouriteIds.isEmpty();
}

public Filter setFavouriteIds(Set<Integer> favouriteIds) {
this.favouriteIds = favouriteIds;
return this;
}

public Filter setFavouriteIds(Integer... favouriteIds) {
this.favouriteIds = Sets.newHashSet(favouriteIds);
return this;
}

public Integer getSortedMetricId() {
return sortedMetricId;
}

public boolean isNumericMetric() {
return isNumericMetric;
}

public boolean isTextSort() {
return !isNumericMetric || sortedByLanguage || sortedByName || sortedByVersion || sortedByKey;
}

public Filter setSortedMetricId(Integer id, boolean isNumericValue, Boolean isVariation) {
unsetSorts();
this.sortedMetricId = id;
this.isNumericMetric = isNumericValue;
this.sortedByMeasureVariation = isVariation;
return this;
}

public boolean isSortedByLanguage() {
return sortedByLanguage;
}

public Filter setSortedByLanguage() {
unsetSorts();
this.sortedByLanguage = true;
return this;
}

public boolean isSortedByName() {
return sortedByName;
}

public boolean isSortedByKey() {
return sortedByKey;
}

public boolean isSortedByVersion() {
return sortedByVersion;
}

public Filter setSortedByVersion() {
unsetSorts();
this.sortedByVersion = true;
return this;
}

public boolean isSorted() {
return isSortedByLanguage() || isSortedByName() || isSortedByKey() || isSortedByDate() || isSortedByVersion() || getSortedMetricId() != null;
}

public boolean isSortedByDate() {
return sortedByDate;
}

public Filter setSortedByDate() {
unsetSorts();
sortedByDate = true;
return this;
}

public Filter setSortedByName() {
unsetSorts();
this.sortedByName = true;
return this;
}

public Filter setSortedByKey() {
unsetSorts();
this.sortedByKey = true;
return this;
}

private void unsetSorts() {
this.sortedByDate = false;
this.sortedByLanguage = false;
this.sortedByName = false;
this.sortedByKey = false;
this.sortedMetricId = null;
this.sortedByVersion = false;
this.isNumericMetric = true;
}

public List<MeasureCriterion> getMeasureCriteria() {
return measureCriteria;
}

public Filter setMeasureCriteria(List<MeasureCriterion> l) {
this.measureCriteria = l;
return this;
}

public Filter addMeasureCriterion(MeasureCriterion c) {
this.measureCriteria.add(c);
return this;
}

public Filter createMeasureCriterionOnValue(Integer metricId, String operator, Double value, Boolean variation) {
this.measureCriteria.add(new MeasureCriterion(metricId, operator, value, variation));
return this;
}

public boolean hasMeasureCriteria() {
return !measureCriteria.isEmpty();
}

protected boolean hasMeasureCriteriaOnMetric(Integer metricId) {
if (metricId != null) {
for (MeasureCriterion criterion : measureCriteria) {
if (metricId.equals(criterion.getMetricId())) {
return true;
}
}
}
return false;
}

public boolean mustJoinMeasuresTable() {
return sortedMetricId != null || hasMeasureCriteria();
}

public boolean isAscendingSort() {
return ascendingSort;
}

public Filter setAscendingSort(boolean b) {
this.ascendingSort = b;
return this;
}

public DateCriterion getDateCriterion() {
return dateCriterion;
}

public Filter setDateCriterion(DateCriterion dc) {
this.dateCriterion = dc;
return this;
}

public Filter setDateCriterion(String operator, Integer daysAgo) {
this.dateCriterion = new DateCriterion().setOperator(operator).setDate(daysAgo);
return this;
}

public String getKeyRegexp() {
return keyRegexp;
}

public Filter setKeyRegexp(String s) {
this.keyRegexp = s;
return this;
}

public String getNameRegexp() {
return nameRegexp;
}

public Filter setNameRegexp(String s) {
this.nameRegexp = s;
return this;
}

public int getPeriodIndex() {
return periodIndex;
}

public void setPeriodIndex(int i) {
this.periodIndex = i;
}

public boolean isOnPeriod() {
return periodIndex > 0;
}

public boolean isOnDirectChildren() {
return onDirectChildren;
}

public void setOnDirectChildren(boolean b) {
this.onDirectChildren = b;
}

static String getVariationColumn(int periodIndex) {
switch (periodIndex) {
case 1:
return "variation_value_1";
case 2:
return "variation_value_2";
case 3:
return "variation_value_3";
case 4:
return "variation_value_4";
case 5:
return "variation_value_5";
default:
return null;
}
}

String getColumnToSort() {
String col = "text_value";
if (isNumericMetric()) {
col = (sortedByMeasureVariation == Boolean.TRUE ? getVariationColumn(periodIndex) : "value");
}
return col;
}

public boolean mustReturnEmptyResult() {
boolean hasCriterionOnVariation = false;
for (MeasureCriterion criterion : measureCriteria) {
if (criterion.isVariation() == Boolean.TRUE) {
hasCriterionOnVariation = true;
}
}
return (hasCriterionOnVariation && !isOnPeriod());
}

@Override
public String toString() {
return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}

public static Filter createForAllQualifiers() {
return new Filter().setQualifiers(
Qualifiers.VIEW, Qualifiers.SUBVIEW,
Qualifiers.PROJECT, Qualifiers.MODULE, Qualifiers.DIRECTORY, Qualifiers.PACKAGE,
Qualifiers.FILE, Qualifiers.CLASS, Qualifiers.UNIT_TEST_FILE, Qualifiers.LIBRARY, Qualifiers.PARAGRAPH);
}
}

+ 0
- 250
sonar-server/src/main/java/org/sonar/server/filters/FilterExecutor.java View File

@@ -1,250 +0,0 @@
/*
* Sonar, open source software quality management tool.
* Copyright (C) 2008-2012 SonarSource
* mailto:contact AT sonarsource DOT com
*
* Sonar is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* Sonar is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with Sonar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package org.sonar.server.filters;

import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.ServerComponent;
import org.sonar.api.database.DatabaseSession;
import org.sonar.api.database.model.Snapshot;
import org.sonar.api.utils.SonarException;
import org.sonar.api.utils.TimeProfiler;
import org.sonar.core.persistence.Database;
import org.sonar.core.persistence.dialect.Dialect;
import org.sonar.core.persistence.dialect.MsSql;

import javax.persistence.Query;

import java.util.Collections;

public class FilterExecutor implements ServerComponent {
private static final Logger LOG = LoggerFactory.getLogger(FilterExecutor.class);
private static final int SQL_INITIAL_SIZE = 1000;
private DatabaseSession session;
private Dialect dialect;

public FilterExecutor(DatabaseSession session, Database database) {
this(session, database.getDialect());
}

@VisibleForTesting
FilterExecutor(DatabaseSession session, Dialect dialect) {
this.session = session;
this.dialect = dialect;
}

public FilterResult execute(Filter filter) {
if (filter.mustReturnEmptyResult()) {
return new FilterResult(filter, Collections.<Object[]> emptyList());
}

String sql = null;
try {
TimeProfiler profiler = new TimeProfiler(FilterExecutor.class).setLevelToDebug().start("Build/execute SQL query");
sql = toSql(filter);
LOG.debug("SQL: " + sql);
Query query = session.getEntityManager().createNativeQuery(sql);
setHqlParameters(filter, query);
FilterResult result = new FilterResult(filter, query.getResultList());
profiler.stop();

profiler.start("Process rows");
result.removeUnvalidRows();
profiler.stop();

profiler.start("Sort rows");
result.sort();
profiler.stop();
return result;

} catch (Exception e) {
throw new SonarException("Fail to execute filter: " + filter.toString() + ", sql=" + sql, e);
}
}

@VisibleForTesting
String toSql(Filter filter) {
StringBuilder sql = new StringBuilder(SQL_INITIAL_SIZE);
addSelectColumns(filter, sql);
addFromClause(filter, sql);
addWhereClause(filter, sql);
return sql.toString();
}

private void addSelectColumns(Filter filter, StringBuilder sql) {
sql.append("SELECT s.id, MAX(s.project_id) as pid, MAX(s.root_project_id) as rpid");
if (filter.isSortedByLanguage()) {
sql.append(", MAX(p.language) as lang ");

} else if (filter.isSortedByName()) {
sql.append(", MAX(p.long_name) as name ");

} else if (filter.isSortedByKey()) {
sql.append(", MAX(p.kee) as kee ");

} else if (filter.isSortedByDate()) {
sql.append(", MAX(s.created_at) as createdat ");

} else if (filter.isSortedByVersion()) {
sql.append(", MAX(s.version) as version ");
}
if (filter.getSortedMetricId() != null) {
sql.append(", MAX(CASE WHEN pm.metric_id=");
sql.append(filter.getSortedMetricId());
sql.append(" THEN ");
sql.append(filter.getColumnToSort());
sql.append(" ELSE NULL END) AS sortvalue");
sql.append(" ");
}
for (int index = 0; index < filter.getMeasureCriteria().size(); index++) {
MeasureCriterion criterion = filter.getMeasureCriteria().get(index);
String column = (criterion.isVariation() ? Filter.getVariationColumn(filter.getPeriodIndex()) : "value");
sql.append(", MAX(CASE WHEN pm.metric_id=");
sql.append(criterion.getMetricId());
sql.append(" AND pm.");
sql.append(column);
sql.append(criterion.getOperator());
sql.append(criterion.getValue());
sql.append(" THEN ");
sql.append(column);
sql.append(" ELSE NULL END) AS crit_");
sql.append(index);
sql.append(" ");
}
}

private void addFromClause(Filter filter, StringBuilder sql) {
sql.append(" FROM snapshots s ");
if (filter.mustJoinMeasuresTable()) {
sql.append(" INNER JOIN project_measures pm ");
if (MsSql.ID.equals(dialect.getId())) {
// SONAR-3422
sql.append(" WITH (INDEX(measures_sid_metric)) ");
}
sql.append(" ON s.id=pm.snapshot_id ");
}
sql.append(" INNER JOIN projects p ON s.project_id=p.id ");
}

private void addWhereClause(Filter filter, StringBuilder sql) {
sql.append(" WHERE ");
if (filter.mustJoinMeasuresTable()) {
if (filter.hasMeasureCriteria()) {
sql.append(" ( ");
int index = 0;
while (index < filter.getMeasureCriteria().size()) {
if (index > 0) {
sql.append(" OR ");
}
MeasureCriterion criterion = filter.getMeasureCriteria().get(index);
String column = (criterion.isVariation() ? Filter.getVariationColumn(filter.getPeriodIndex()) : "value");
sql.append("(pm.metric_id=").append(criterion.getMetricId()).append(" and pm.").append(column)
.append(criterion.getOperator()).append(criterion.getValue()).append(")");
index++;
}

if (filter.getSortedMetricId() != null && !filter.hasMeasureCriteriaOnMetric(filter.getSortedMetricId())) {
sql.append(" OR (pm.metric_id=").append(filter.getSortedMetricId()).append(") ");
}

sql.append(" ) AND ");
}
sql.append(" pm.rule_id IS NULL AND pm.rule_priority IS NULL");
sql.append(" AND pm.characteristic_id IS NULL");
sql.append(" AND pm.person_id IS NULL");
sql.append(" AND ");
}
sql.append(" s.status=:status AND s.islast=:islast ");
if (filter.getScopes() != null) {
sql.append(filter.getScopes().isEmpty() ? " AND s.scope IS NULL " : " AND s.scope IN (:scopes) ");
}
if (filter.hasQualifiers()) {
sql.append(" AND s.qualifier IN (:qualifiers) ");
} else if (!filter.isOnDirectChildren()) {
// no criteria -> we should not display all rows but no rows
sql.append(" AND s.qualifier IS NULL ");
}
if (filter.hasLanguages()) {
sql.append(" AND p.language IN (:languages) ");
}
if (filter.getFavouriteIds() != null) {
sql.append(filter.getFavouriteIds().isEmpty() ? " AND s.project_id IS NULL " : " AND s.project_id IN (:favourites) ");
}
if (filter.hasBaseSnapshot()) {
if (filter.isOnDirectChildren()) {
sql.append(" AND s.parent_snapshot_id=:parent_sid ");
} else {
sql.append(" AND s.root_snapshot_id=:root_sid AND s.path LIKE :path ");
}
}
if (filter.getDateCriterion() != null) {
sql.append(" AND s.created_at");
sql.append(filter.getDateCriterion().getOperator());
sql.append(" :date ");
}
if (StringUtils.isNotBlank(filter.getKeyRegexp())) {
sql.append(" AND UPPER(p.kee) LIKE :kee");
}
if (StringUtils.isNotBlank(filter.getNameRegexp())) {
sql.append(" AND UPPER(p.long_name) LIKE :name");
}
if (!filter.hasBaseSnapshot()) {
sql.append(" AND p.copy_resource_id IS NULL ");
}
sql.append(" GROUP BY s.id");
}

private void setHqlParameters(Filter filter, Query query) {
query.setParameter("status", Snapshot.STATUS_PROCESSED);
query.setParameter("islast", true);
if (filter.hasScopes()) {
query.setParameter("scopes", filter.getScopes());
}
if (filter.hasQualifiers()) {
query.setParameter("qualifiers", filter.getQualifiers());
}
if (filter.hasLanguages()) {
query.setParameter("languages", filter.getLanguages());
}
if (filter.hasFavouriteIds()) {
query.setParameter("favourites", filter.getFavouriteIds());
}
if (filter.getDateCriterion() != null) {
query.setParameter("date", filter.getDateCriterion().getDate());
}
if (filter.hasBaseSnapshot()) {
if (filter.isOnDirectChildren()) {
query.setParameter("parent_sid", filter.getBaseSnapshotId());
} else {
query.setParameter("root_sid", filter.getRootSnapshotId());
query.setParameter("path", new StringBuilder().append(
filter.getBaseSnapshotPath()).append(filter.getBaseSnapshotId()).append(".%").toString());
}
}
if (StringUtils.isNotBlank(filter.getKeyRegexp())) {
query.setParameter("kee", StringUtils.upperCase(StringUtils.replaceChars(filter.getKeyRegexp(), '*', '%')));
}
if (StringUtils.isNotBlank(filter.getNameRegexp())) {
query.setParameter("name", StringUtils.upperCase(StringUtils.replaceChars(filter.getNameRegexp(), '*', '%')));
}
}
}

+ 0
- 142
sonar-server/src/main/java/org/sonar/server/filters/FilterResult.java View File

@@ -1,142 +0,0 @@
/*
* Sonar, open source software quality management tool.
* Copyright (C) 2008-2012 SonarSource
* mailto:contact AT sonarsource DOT com
*
* Sonar is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* Sonar is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with Sonar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package org.sonar.server.filters;

import com.google.common.collect.Ordering;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

public class FilterResult {
private List<Object[]> rows;
private Filter filter;
public static final int SORTED_COLUMN_INDEX = 3;

public FilterResult(Filter filter, List<Object[]> rows) {
this.rows = new ArrayList<Object[]>(rows);
this.filter = filter;
}

/**
* @return a list of arrays
*/
public List<Object[]> getRows() {
return rows;
}

public int size() {
return rows.size();
}

public Integer getSnapshotId(Object[] row) {
return (Integer) row[getSnapshotIdIndex()];
}

public Integer getProjectId(Object[] row) {
return (Integer) row[getProjectIdIndex()];
}

public Integer getRootProjectId(Object[] row) {
return (Integer) row[getRootProjectIdIndex()];
}

public int getSnapshotIdIndex() {
return 0;
}

public int getProjectIdIndex() {
return 1;
}

public int getRootProjectIdIndex() {
return 2;
}

public void sort() {
if (filter.isSorted()) {
Comparator<Object[]> comparator = (filter.isTextSort() ? new StringIgnoreCaseComparator(SORTED_COLUMN_INDEX) : new NumericComparator(SORTED_COLUMN_INDEX));
if (!filter.isAscendingSort()) {
comparator = Ordering.from(comparator).reverse();
}
Collections.sort(rows, comparator);
}
}

public void removeUnvalidRows() {
int numberOfCriteria = filter.getMeasureCriteria().size();
if (numberOfCriteria > 0) {
int fromColumnIndex = (filter.isSorted() ? SORTED_COLUMN_INDEX + 1 : SORTED_COLUMN_INDEX);
for (Iterator<Object[]> it = rows.iterator(); it.hasNext(); ) {
Object[] row = it.next();
boolean remove = false;
for (int index = 0; index < numberOfCriteria; index++) {
if (row[fromColumnIndex + index] == null) {
remove = true;
}
}
if (remove) {
it.remove();
}
}
}
}

static final class NumericComparator implements Comparator<Object[]>, Serializable {
private static final long serialVersionUID = 4627704879575964978L;
private int index;

NumericComparator(int index) {
this.index = index;
}

public int compare(Object[] a1, Object[] a2) {
Comparable c1 = (Comparable) a1[index];
Comparable o2 = (Comparable) a2[index];

return (c1 == null ? -1 : (o2 == null ? 1 : c1.compareTo(o2)));
}
}

static final class StringIgnoreCaseComparator implements Comparator<Object[]>, Serializable {
private static final long serialVersionUID = 963926659201833101L;
private int index;

StringIgnoreCaseComparator(int index) {
this.index = index;
}

public int compare(Object[] o1, Object[] o2) {
String s1 = (String) o1[index];
if (s1 == null) {
return -1;
}
String s2 = (String) o2[index];
if (s2 == null) {
return 1;
}
return s1.compareToIgnoreCase(s2);
}
}
}


+ 0
- 76
sonar-server/src/main/java/org/sonar/server/filters/MeasureCriterion.java View File

@@ -1,76 +0,0 @@
/*
* Sonar, open source software quality management tool.
* Copyright (C) 2008-2012 SonarSource
* mailto:contact AT sonarsource DOT com
*
* Sonar is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* Sonar is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with Sonar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package org.sonar.server.filters;

import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;

public class MeasureCriterion {

private Integer metricId;
private String operator;
private Double value;
private Boolean variation;

public MeasureCriterion(Integer metricId, String operator, Double value, Boolean variation) {
this.metricId = metricId;
this.operator = operator;
this.value = value;
this.variation = variation;
}

public Integer getMetricId() {
return metricId;
}

public void setMetricId(Integer metricId) {
this.metricId = metricId;
}

public String getOperator() {
return operator;
}

public void setOperator(String operator) {
this.operator = operator;
}

public Double getValue() {
return value;
}

public void setValue(Double value) {
this.value = value;
}

public boolean isVariation() {
return variation==Boolean.TRUE;
}

public MeasureCriterion setVariation(Boolean b) {
this.variation = b;
return this;
}

@Override
public String toString() {
return new ReflectionToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).toString();
}
}

+ 9
- 19
sonar-server/src/main/java/org/sonar/server/platform/Platform.java View File

@@ -42,13 +42,12 @@ import org.sonar.core.config.Logback;
import org.sonar.core.i18n.GwtI18n;
import org.sonar.core.i18n.I18nManager;
import org.sonar.core.i18n.RuleI18nManager;
import org.sonar.core.measure.MeasureFilterDecoder;
import org.sonar.core.measure.MeasureFilterEngine;
import org.sonar.core.measure.MeasureFilterExecutor;
import org.sonar.core.metric.DefaultMetricFinder;
import org.sonar.core.notification.DefaultNotificationManager;
import org.sonar.core.persistence.DaoUtils;
import org.sonar.core.persistence.DatabaseMigrator;
import org.sonar.core.persistence.DatabaseVersion;
import org.sonar.core.persistence.DefaultDatabase;
import org.sonar.core.persistence.MyBatis;
import org.sonar.core.persistence.*;
import org.sonar.core.qualitymodel.DefaultModelFinder;
import org.sonar.core.rule.DefaultRuleFinder;
import org.sonar.core.user.DefaultUserFinder;
@@ -65,25 +64,14 @@ import org.sonar.server.charts.ChartFactory;
import org.sonar.server.configuration.Backup;
import org.sonar.server.configuration.ProfilesManager;
import org.sonar.server.database.EmbeddedDatabaseFactory;
import org.sonar.server.filters.FilterExecutor;
import org.sonar.server.notifications.NotificationService;
import org.sonar.server.notifications.reviews.ReviewsNotificationManager;
import org.sonar.server.plugins.ApplicationDeployer;
import org.sonar.server.plugins.DefaultServerPluginRepository;
import org.sonar.server.plugins.PluginDeployer;
import org.sonar.server.plugins.PluginDownloader;
import org.sonar.server.plugins.ServerExtensionInstaller;
import org.sonar.server.plugins.UpdateCenterClient;
import org.sonar.server.plugins.UpdateCenterMatrixFactory;
import org.sonar.server.plugins.*;
import org.sonar.server.qualitymodel.DefaultModelManager;
import org.sonar.server.rules.ProfilesConsole;
import org.sonar.server.rules.RulesConsole;
import org.sonar.server.startup.*;
import org.sonar.server.ui.CodeColorizers;
import org.sonar.server.ui.JRubyI18n;
import org.sonar.server.ui.PageDecorations;
import org.sonar.server.ui.SecurityRealmFactory;
import org.sonar.server.ui.Views;
import org.sonar.server.ui.*;

import javax.servlet.ServletContext;

@@ -203,7 +191,6 @@ public final class Platform {
servicesContainer.addSingleton(UpdateCenterMatrixFactory.class);
servicesContainer.addSingleton(PluginDownloader.class);
servicesContainer.addSingleton(ServerIdGenerator.class);
servicesContainer.addComponent(FilterExecutor.class, false);
servicesContainer.addSingleton(DefaultModelFinder.class); // depends on plugins
servicesContainer.addSingleton(DefaultModelManager.class);
servicesContainer.addSingleton(Plugins.class);
@@ -233,6 +220,9 @@ public final class Platform {
servicesContainer.addSingleton(NewUserNotifier.class);
servicesContainer.addSingleton(SettingsChangeNotifier.class);
servicesContainer.addSingleton(PageDecorations.class);
servicesContainer.addSingleton(MeasureFilterDecoder.class);
servicesContainer.addSingleton(MeasureFilterExecutor.class);
servicesContainer.addSingleton(MeasureFilterEngine.class);

// Notifications
servicesContainer.addSingleton(EmailSettings.class);

+ 12
- 26
sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java View File

@@ -38,16 +38,14 @@ import org.sonar.api.resources.ResourceTypes;
import org.sonar.api.rules.RulePriority;
import org.sonar.api.rules.RuleRepository;
import org.sonar.api.utils.ValidationMessages;
import org.sonar.api.web.Footer;
import org.sonar.api.web.NavigationSection;
import org.sonar.api.web.Page;
import org.sonar.api.web.RubyRailsWebservice;
import org.sonar.api.web.Widget;
import org.sonar.api.web.*;
import org.sonar.api.workflow.Review;
import org.sonar.api.workflow.internal.DefaultReview;
import org.sonar.api.workflow.internal.DefaultWorkflowContext;
import org.sonar.api.workflow.screen.Screen;
import org.sonar.core.i18n.RuleI18nManager;
import org.sonar.core.measure.MeasureFilterEngine;
import org.sonar.core.measure.MeasureFilterRow;
import org.sonar.core.persistence.Database;
import org.sonar.core.persistence.DatabaseMigrator;
import org.sonar.core.purge.PurgeDao;
@@ -57,26 +55,14 @@ import org.sonar.core.workflow.WorkflowEngine;
import org.sonar.markdown.Markdown;
import org.sonar.server.configuration.Backup;
import org.sonar.server.configuration.ProfilesManager;
import org.sonar.server.filters.Filter;
import org.sonar.server.filters.FilterExecutor;
import org.sonar.server.filters.FilterResult;
import org.sonar.server.notifications.reviews.ReviewsNotificationManager;
import org.sonar.server.platform.NewUserNotifier;
import org.sonar.server.platform.Platform;
import org.sonar.server.platform.ServerIdGenerator;
import org.sonar.server.platform.ServerSettings;
import org.sonar.server.platform.SettingsChangeNotifier;
import org.sonar.server.plugins.DefaultServerPluginRepository;
import org.sonar.server.plugins.PluginDeployer;
import org.sonar.server.plugins.PluginDownloader;
import org.sonar.server.plugins.UpdateCenterMatrix;
import org.sonar.server.plugins.UpdateCenterMatrixFactory;
import org.sonar.server.platform.*;
import org.sonar.server.plugins.*;
import org.sonar.server.rules.ProfilesConsole;
import org.sonar.server.rules.RulesConsole;
import org.sonar.updatecenter.common.Version;

import javax.annotation.Nullable;

import java.net.InetAddress;
import java.sql.Connection;
import java.util.Collection;
@@ -97,8 +83,8 @@ public final class JRubyFacade {
return getContainer().getComponentByType(componentType);
}

public FilterResult executeFilter(Filter filter) {
return get(FilterExecutor.class).execute(filter);
public List<MeasureFilterRow> executeMeasureFilter(String json, @Nullable Long userId) throws Exception {
return get(MeasureFilterEngine.class).execute(json, userId);
}

public Collection<ResourceType> getResourceTypesForFilter() {
@@ -313,7 +299,7 @@ public final class JRubyFacade {

public void ruleSeverityChanged(int parentProfileId, int activeRuleId, int oldSeverityId, int newSeverityId, String userName) {
getProfilesManager().ruleSeverityChanged(parentProfileId, activeRuleId, RulePriority.values()[oldSeverityId],
RulePriority.values()[newSeverityId], userName);
RulePriority.values()[newSeverityId], userName);
}

public void ruleDeactivated(int parentProfileId, int deactivatedRuleId, String userName) {
@@ -509,10 +495,10 @@ public final class JRubyFacade {
// notifier is null when creating the administrator in the migration script 011.
if (notifier != null) {
notifier.onNewUser(NewUserHandler.Context.builder()
.setLogin(fields.get("login"))
.setName(fields.get("name"))
.setEmail(fields.get("email"))
.build());
.setLogin(fields.get("login"))
.setName(fields.get("name"))
.setEmail(fields.get("email"))
.build());
}
}
}

+ 1
- 1
sonar-server/src/main/webapp/WEB-INF/app/models/filter.rb View File

@@ -168,7 +168,7 @@ class Filter < ActiveRecord::Base
def on_direct_children?
if resource_id
c = criterion('direct-children')
c && c.text_value=='true'
c ? c.text_value=='true' : false
else
false
end

+ 5
- 15
sonar-server/src/main/webapp/WEB-INF/app/models/filter_context.rb View File

@@ -135,25 +135,15 @@

private

def extract_snapshot_ids(sql_rows)
def extract_snapshot_ids(filter_rows)
sids=[]
project_ids=sql_rows.map{|r| r[2] ? to_integer(r[2]) : to_integer(r[1])}.compact.uniq
project_ids=filter_rows.map{|row| row.getResourceRootId()}.compact.uniq
authorized_pids=select_authorized(:user, project_ids)
sql_rows.each do |row|
pid=(row[2] ? to_integer(row[2]) : to_integer(row[1]))
if authorized_pids.include?(pid)
sids<<to_integer(row[0])
filter_rows.each do |row|
if authorized_pids.include?(row.getResourceRootId())
sids<<row.getSnapshotId()
end
end
sids
end

def to_integer(obj)
if obj.is_a?(Fixnum)
obj
else
# java.math.BigDecimal
obj.intValue()
end
end
end

+ 38
- 61
sonar-server/src/main/webapp/WEB-INF/app/models/filters.rb View File

@@ -21,82 +21,71 @@ class Filters

def self.execute(filter, authenticated_system, options={})
filter_context = FilterContext.new(filter, options)
java_filter=Java::OrgSonarServerFilters::Filter.new

filter_json={}

#----- FILTER ON RESOURCES
if filter.resource_id
snapshot=Snapshot.find(:first, :conditions => {:project_id => filter.resource_id, :islast => true})
if snapshot
java_filter.setPath(snapshot.root_snapshot_id, snapshot.id, snapshot.path)
else
java_filter.setPath(-1, -1, '')
end
filter_json[:base]=filter.resource.key
end

if filter.favourites
java_filter.setFavouriteIds((authenticated_system.current_user.favourite_ids||[]).to_java(:Integer))
filter_json[:favourites]=true
end

date_criterion=filter.criterion('date')
if date_criterion
java_filter.setDateCriterion(date_criterion.operator, date_criterion.value.to_i)
if date_criterion.operator=='<'
filter_json[:beforeDays]=date_criterion.value.to_i
else
filter_json[:afterDays]=date_criterion.value.to_i
end
end

key_criterion=filter.criterion('key')
if key_criterion
java_filter.setKeyRegexp(key_criterion.text_value)
#java_filter.setKeyRegexp(key_criterion.text_value)
end

name_criterion=filter.criterion('name')
if name_criterion
java_filter.setNameRegexp(name_criterion.text_value)
filter_json[:name]=name_criterion.text_value
end

qualifier_criterion=filter.criterion('qualifier')
if qualifier_criterion
java_filter.setQualifiers(qualifier_criterion.text_values.to_java(:String))
else
java_filter.setQualifiers([].to_java(:String))
filter_json[:qualifiers]=qualifier_criterion.text_values
end

java_filter.setOnDirectChildren(filter.on_direct_children?)
filter_json[:onBaseChildren]=filter.on_direct_children?

language_criterion=filter.criterion('language')
if language_criterion
java_filter.setLanguages(language_criterion.text_values.to_java :String)
filter_json[:languages]=language_criterion.text_values
end


#----- FILTER ON MEASURES
filter.measure_criteria.each do |c|
java_filter.createMeasureCriterionOnValue(c.metric.id, c.operator, c.value, c.variation)
filter_json[:conditions]=filter.measure_criteria.map do |c|
hash = {:metric => c.metric.key, :op => c.operator, :val => c.value}
if c.variation
hash[:period] = filter_context.period_index || -1
end
hash
end


#----- SORTED COLUMN

if filter_context.sorted_column_id
filter.sorted_column=filter_context.sorted_column_id
end
if filter.sorted_column
if filter.sorted_column.on_name?
java_filter.setSortedByName()

elsif filter.sorted_column.on_date?
java_filter.setSortedByDate()

elsif filter.sorted_column.on_version?
java_filter.setSortedByVersion()

elsif filter.sorted_column.on_language?
java_filter.setSortedByLanguage()

elsif filter.sorted_column.on_key?
java_filter.setSortedByKey()

elsif filter.sorted_column.on_metric? && filter.sorted_column.metric
metric=filter.sorted_column.metric
java_filter.setSortedMetricId(metric.id, metric.numeric?, filter.sorted_column.variation)
filter_json[:sortField]=filter.sorted_column.family.upcase
if filter.sorted_column.on_metric? && filter.sorted_column.metric
filter_json[:sortMetric]=filter.sorted_column.metric.key
if filter.sorted_column.variation
filter_json[:sortPeriod]=filter_context.period_index || -1
end
end
end

@@ -104,51 +93,39 @@ class Filters
#----- SORTING DIRECTION
if filter.sorted_column
if filter_context.ascending_sort.nil?
java_filter.setAscendingSort(filter.sorted_column.ascending?)
filter_json[:sortAsc]=filter.sorted_column.ascending?
else
filter.sorted_column.ascending=filter_context.ascending_sort
java_filter.setAscendingSort(filter.sorted_column.ascending?)
filter_json[:sortAsc]=filter.sorted_column.ascending?
end

if filter_context.ascending_sort
filter.sorted_column.ascending=filter_context.ascending_sort
end
java_filter.setAscendingSort(filter.sorted_column.ascending?)
filter_json[:sortAsc]=filter.sorted_column.ascending?
end

#----- VARIATION
java_filter.setPeriodIndex(filter_context.period_index)

#----- EXECUTION
java_result=Java::OrgSonarServerUi::JRubyFacade.getInstance().execute_filter(java_filter)
snapshot_ids=extract_snapshot_ids(java_result.getRows(), authenticated_system)
user=authenticated_system.current_user
rows=Api::Utils.java_facade.executeMeasureFilter(filter_json.to_json, user ? user.id : nil)
snapshot_ids=extract_snapshot_ids(rows, authenticated_system)

has_security_exclusions=(snapshot_ids.size < java_result.size())
has_security_exclusions=(snapshot_ids.size < rows.size)
filter_context.process_results(snapshot_ids, has_security_exclusions)
filter_context
end

private

def self.extract_snapshot_ids(sql_rows, authenticated_system)
def self.extract_snapshot_ids(rows, authenticated_system)
sids=[]
project_ids=sql_rows.map { |r| r[2] ? to_integer(r[2]) : to_integer(r[1]) }.compact.uniq
project_ids=rows.map { |row| row.getResourceRootId() }.compact.uniq
authorized_pids=authenticated_system.select_authorized(:user, project_ids)
sql_rows.each do |row|
pid=(row[2] ? to_integer(row[2]) : to_integer(row[1]))
if authorized_pids.include?(pid)
sids<<to_integer(row[0])
rows.each do |row|
if authorized_pids.include?(row.getResourceRootId())
sids<<row.getSnapshotId()
end
end
sids
end

def self.to_integer(obj)
if obj.is_a?(Fixnum)
obj
else
# java.math.BigDecimal
obj.intValue()
end
end
end

+ 0
- 47
sonar-server/src/test/java/org/sonar/server/filters/DateCriterionTest.java View File

@@ -1,47 +0,0 @@
/*
* Sonar, open source software quality management tool.
* Copyright (C) 2008-2012 SonarSource
* mailto:contact AT sonarsource DOT com
*
* Sonar is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* Sonar is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with Sonar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package org.sonar.server.filters;

import org.apache.commons.lang.time.DateUtils;
import org.junit.Test;

import java.util.Date;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

public class DateCriterionTest {
@Test
public void ignoreTime() {
DateCriterion criterion = new DateCriterion().setDate(3);
Date date = criterion.getDate();
assertThat(date.getHours(), is(0));
assertThat(date.getMinutes(), is(0));
}

@Test
public void testDaysAgo() {
DateCriterion criterion = new DateCriterion().setDate(3);
Date date = criterion.getDate();
assertThat(date.getMinutes(), is(0));
assertThat(date.getHours(), is(0));
assertThat(DateUtils.isSameDay(date, DateUtils.addDays(new Date(), -3)), is(true));
}
}

+ 0
- 361
sonar-server/src/test/java/org/sonar/server/filters/FilterExecutorTest.java View File

@@ -1,361 +0,0 @@
/*
* Sonar, open source software quality management tool.
* Copyright (C) 2008-2012 SonarSource
* mailto:contact AT sonarsource DOT com
*
* Sonar is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* Sonar is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with Sonar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package org.sonar.server.filters;

import com.google.common.collect.Sets;
import org.junit.Test;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.resources.Resource;
import org.sonar.core.persistence.dialect.H2;
import org.sonar.core.persistence.dialect.MsSql;
import org.sonar.jpa.test.AbstractDbUnitTestCase;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import static org.fest.assertions.Assertions.assertThat;
import static org.fest.assertions.Fail.fail;

public class FilterExecutorTest extends AbstractDbUnitTestCase {

@Test
public void mustDefineAtLeastOneQualifier() {
setupData("shared");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
FilterResult result = executor.execute(new Filter());
assertThat(result.size()).isEqualTo(0);// no qualifiers
}

@Test
public void filterOnScopes() {
setupData("shared");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
FilterResult result = executor.execute(Filter.createForAllQualifiers().setScopes(Sets.newHashSet(Resource.SCOPE_SPACE)));
assertSnapshotIds(result, 4);
}

@Test
public void filterOnQualifiers() {
setupData("shared");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
FilterResult result = executor.execute(new Filter().setQualifiers(Sets.newHashSet(Resource.QUALIFIER_PROJECT, Resource.QUALIFIER_MODULE)));
assertSnapshotIds(result, 2, 3);
}

@Test
public void filterOnLanguages() {
setupData("shared");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
FilterResult result = executor.execute(Filter.createForAllQualifiers().setLanguages(Sets.newHashSet("java")));
assertSnapshotIds(result, 2, 4);
}

@Test
public void filterOnDate() throws ParseException {
setupData("shared");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm").parse("2008-12-26 00:00");
FilterResult result = executor.execute(Filter.createForAllQualifiers().setDateCriterion(new DateCriterion(">", date)));
assertSnapshotIds(result, 3);
}

@Test
public void filterOnDateIncludesTime() throws ParseException {
setupData("shared");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm").parse("2008-12-25 03:00");
FilterResult result = executor.execute(Filter.createForAllQualifiers().setDateCriterion(new DateCriterion("<", date)));
assertSnapshotIds(result, 2, 4);
}

@Test
public void filterOnBaseSnapshot() {
setupData("shared");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
FilterResult result = executor.execute(Filter.createForAllQualifiers().setPath(2, 2, ""));
assertSnapshotIds(result, 4);
}

@Test
public void sortByName() {
setupData("shared");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
FilterResult result = executor.execute(Filter.createForAllQualifiers().setSortedByName());
assertSortedSnapshotIds(result, 2, 4, 3);
}

@Test
public void sortByKey() {
setupData("shared");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
FilterResult result = executor.execute(Filter.createForAllQualifiers().setSortedByKey());
assertSortedSnapshotIds(result, 3, 2, 4);
}

@Test
public void sortByDate() {
setupData("shared");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
FilterResult result = executor.execute(Filter.createForAllQualifiers().setSortedByDate());
assertSortedSnapshotIds(result, 2, 4, 3);
}

@Test
public void sortByDescendingDate() {
setupData("shared");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
FilterResult result = executor.execute(Filter.createForAllQualifiers().setSortedByDate().setAscendingSort(false));
assertSortedSnapshotIds(result, 3, 4, 2);
}

@Test
public void sortByAscendingDate() {
setupData("shared");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
FilterResult result = executor.execute(Filter.createForAllQualifiers().setSortedByDate().setAscendingSort(true));
assertSortedSnapshotIds(result, 2, 4, 3);
}

@Test
public void sortByAscendingMeasureValue() {
setupData("shared", "measures");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
Filter filter = new Filter()
.setQualifiers(Sets.newHashSet(Qualifiers.CLASS))
.setSortedMetricId(2, true, false);

FilterResult result = executor.execute(filter);
assertSortedSnapshotIds(result, 6, 5);
}

@Test
public void sortByDecendingMeasureValue() {
setupData("shared", "measures");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
Filter filter = new Filter()
.setQualifiers(Sets.newHashSet(Qualifiers.CLASS))
.setSortedMetricId(2, true, false)
.setAscendingSort(false);

FilterResult result = executor.execute(filter);
assertSortedSnapshotIds(result, 5, 6);
}

@Test
public void applySingleMeasureCriterion() {
setupData("shared", "measures");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
Filter filter = new Filter()
.setQualifiers(Sets.newHashSet(Qualifiers.CLASS))
.addMeasureCriterion(new MeasureCriterion(2, ">", 50.0, false));

FilterResult result = executor.execute(filter);
assertSnapshotIds(result, 5);
}

@Test
public void applyManyMeasureCriteria() {
setupData("shared", "measures");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
Filter filter = new Filter()
.setQualifiers(Sets.newHashSet(Qualifiers.CLASS))
.addMeasureCriterion(new MeasureCriterion(2, ">", 50.0, false))
.addMeasureCriterion(new MeasureCriterion(1, ">", 100.0, false));

FilterResult result = executor.execute(filter);
assertSnapshotIds(result, 5);
}

@Test
public void criteriaAreExclusive() {
setupData("shared", "measures");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
Filter filter = new Filter()
.setQualifiers(Sets.newHashSet(Qualifiers.CLASS))
.addMeasureCriterion(new MeasureCriterion(2, ">", 50.0, false))
.addMeasureCriterion(new MeasureCriterion(1, "<", 100.0, false));

FilterResult result = executor.execute(filter);
assertThat(result.size()).isEqualTo(0);
}

@Test
public void sortAndFilterMeasures() {
setupData("shared", "measures");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
Filter filter = new Filter()
.setQualifiers(Sets.newHashSet(Qualifiers.CLASS))
.addMeasureCriterion(new MeasureCriterion(2, ">", 5.0, false))
.addMeasureCriterion(new MeasureCriterion(1, ">", 5.0, false))
.setSortedMetricId(2, true, false); // sort by coverage

FilterResult result = executor.execute(filter);
assertSnapshotIds(result, 6, 5);
}

@Test
public void sortDescendingAndFilterMeasures() {
setupData("shared", "measures");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
Filter filter = new Filter()
.setQualifiers(Sets.newHashSet(Qualifiers.CLASS))
.addMeasureCriterion(new MeasureCriterion(2, ">", 5.0, false)) // filter on coverage
.addMeasureCriterion(new MeasureCriterion(1, ">", 5.0, false)) // filter on lines
.setSortedMetricId(2, true, false) // sort by coverage
.setAscendingSort(false);

FilterResult result = executor.execute(filter);
assertSnapshotIds(result, 5, 6);
}

@Test
public void filterByResourceKey() {
setupData("shared");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
FilterResult result = executor.execute(Filter.createForAllQualifiers().setKeyRegexp("*:org.sonar.*"));
assertSnapshotIds(result, 4);
}

@Test
public void filterByResourceKeyIsCaseInsensitive() {
setupData("shared");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
FilterResult result = executor.execute(Filter.createForAllQualifiers().setKeyRegexp("*:ORG.SonAR.*"));
assertSnapshotIds(result, 4);
}

@Test
public void filterByMissingMeasureValue() {
setupData("shared", "measures");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
Filter filter = new Filter()
.setQualifiers(Sets.newHashSet(Qualifiers.CLASS))
.addMeasureCriterion(new MeasureCriterion(3, ">", 0.0, false)); // filter on duplicated lines

FilterResult result = executor.execute(filter);
assertSnapshotIds(result, 6);
}

@Test
public void filterByMissingMeasureValues() {
setupData("shared", "measures");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
Filter filter = new Filter()
.setQualifiers(Sets.newHashSet(Qualifiers.CLASS))
.addMeasureCriterion(new MeasureCriterion(1, ">", 0.0, false)) // filter on lines
.addMeasureCriterion(new MeasureCriterion(3, ">", 0.0, false)); // filter on duplicated lines

FilterResult result = executor.execute(filter);
assertSnapshotIds(result, 6);
}

@Test
public void sortByMissingMeasureValue() {
setupData("shared", "measures");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
Filter filter = new Filter()
.setQualifiers(Sets.newHashSet(Qualifiers.CLASS))
.setSortedMetricId(3, true, false); // sort by duplicated lines

FilterResult result = executor.execute(filter);
assertSnapshotIds(result, 5, 6);
}

@Test
public void filterByMeasureValueAndSortOnOtherMetric() {
setupData("shared", "measures");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
Filter filter = new Filter()
.setQualifiers(Sets.newHashSet(Qualifiers.CLASS))
.addMeasureCriterion(new MeasureCriterion(1, ">", 0.0, false)) // lines > 0
.setSortedMetricId(2, true, false); // sort by coverage

FilterResult result = executor.execute(filter);
assertSnapshotIds(result, 6, 5);
}

@Test
public void intersectionOfCriteriaOnSameMetric() {
setupData("shared", "measures");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
Filter filter = new Filter()
.setQualifiers(Sets.newHashSet(Qualifiers.CLASS))
.addMeasureCriterion(new MeasureCriterion(1, ">", 400.0, false)) // lines > 400
.addMeasureCriterion(new MeasureCriterion(1, "<", 600.0, false)); // lines > 400

FilterResult result = executor.execute(filter);
assertSnapshotIds(result, 5);
}

@Test
public void ignoreProjectCopiesOfViews() {
setupData("views");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
Filter filter = new Filter()
.setQualifiers(Sets.newHashSet(Qualifiers.PROJECT));

FilterResult result = executor.execute(filter);
assertSnapshotIds(result, 1); // the "project copy" with id 4 is ignored
}

@Test
public void loadProjectCopiesIfPathIsAView() {
setupData("views");
FilterExecutor executor = new FilterExecutor(getSession(), new H2());
Filter filter = new Filter()
.setPath(2, 2, "")
.setQualifiers(Sets.newHashSet(Qualifiers.SUBVIEW, Qualifiers.PROJECT));

FilterResult result = executor.execute(filter);
assertSnapshotIds(result, 3, 4);
}

@Test
public void explicitelyUseIndexOnMsSql() {
Filter filter = new Filter().addMeasureCriterion(new MeasureCriterion(1, ">", 400.0, false));

String sql = new FilterExecutor(getSession(), new MsSql()).toSql(filter);
assertThat(sql).contains(" WITH (INDEX(measures_sid_metric)) ");

sql = new FilterExecutor(getSession(), new H2()).toSql(filter);
assertThat(sql).doesNotContain(" WITH (INDEX(measures_sid_metric)) ");
}

private void assertSnapshotIds(FilterResult result, int... snapshotIds) {
assertThat(result.size()).isEqualTo(snapshotIds.length);
for (int snapshotId : snapshotIds) {
boolean found = false;
for (Object[] row : result.getRows()) {
found |= result.getSnapshotId(row) == snapshotId;
}
if (!found) {
fail("Snapshot id not found in results: " + snapshotId);
}
}
}

private void assertSortedSnapshotIds(FilterResult result, int... snapshotIds) {
assertThat(result.size()).isEqualTo(snapshotIds.length);
for (int index = 0; index < snapshotIds.length; index++) {
assertThat(result.getSnapshotId(result.getRows().get(index))).isEqualTo(snapshotIds[index]);
}
}
}

+ 0
- 46
sonar-server/src/test/java/org/sonar/server/filters/FilterResultTest.java View File

@@ -1,46 +0,0 @@
/*
* Sonar, open source software quality management tool.
* Copyright (C) 2008-2012 SonarSource
* mailto:contact AT sonarsource DOT com
*
* Sonar is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* Sonar is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with Sonar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package org.sonar.server.filters;

import org.junit.Test;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNull.nullValue;
import static org.junit.Assert.assertThat;

public class FilterResultTest {

@Test
public void sortWithNullElements() {
List<String[]> list = Arrays.asList(new String[]{"foo"}, new String[]{null}, new String[]{"bar"}, new String[]{null}, new String[]{null}, new String[]{"toto"});
Collections.sort(list, new FilterResult.NumericComparator(0));

assertThat(list.get(0)[0], nullValue());
assertThat(list.get(1)[0], nullValue());
assertThat(list.get(2)[0], nullValue());
assertThat(list.get(3)[0], is("bar"));
assertThat(list.get(4)[0], is("foo"));
assertThat(list.get(5)[0], is("toto"));
}
}

+ 0
- 68
sonar-server/src/test/resources/org/sonar/server/filters/FilterExecutorTest/measures.xml View File

@@ -1,68 +0,0 @@
<dataset>
<metrics delete_historical_data="[null]" id="1" name="lines" val_type="FLOAT" description="Lines" domain="Size"
short_name="Lines" qualitative="false" user_managed="false" enabled="true" origin="JAV" worst_value="[null]"
optimized_best_value="[null]" best_value="[null]" direction="1" hidden="false"/>

<metrics delete_historical_data="[null]" id="2" name="coverage" val_type="PERCENT" description="Coverage" domain="Tests"
short_name="Coverage" qualitative="true" user_managed="false" enabled="true" origin="JAV" worst_value="[null]"
optimized_best_value="[null]" best_value="[null]" direction="1" hidden="false"/>

<metrics delete_historical_data="[null]" id="3" name="duplicated_lines" val_type="INT" description="Duplicated lines" domain="Duplications"
short_name="Duplications" qualitative="false" user_managed="false" enabled="true" origin="JAV" worst_value="[null]"
optimized_best_value="[null]" best_value="[null]" direction="1" hidden="false"/>


<!-- Java classes -->
<projects long_name="org.sonar.foo:File1" id="4" scope="FIL" kee="project:java:org.sonar.foo:File1" qualifier="CLA" name="File1"
root_id="1"
description="[null]" enabled="true" language="java" copy_resource_id="[null]" person_id="[null]"/>

<projects long_name="org.sonar.foo:File2" id="5" scope="FIL" kee="project:java:org.sonar.foo:File2" qualifier="CLA" name="File2"
root_id="1"
description="[null]" enabled="true" language="java" copy_resource_id="[null]" person_id="[null]"/>


<snapshots purge_status="[null]" period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="5" created_at="2008-12-25 01:00:01.00" build_date="2008-12-25 01:00:01.00" version="1.0" project_id="4" scope="FIL" qualifier="CLA"
root_project_id="1" root_snapshot_id="2" parent_snapshot_id="4" STATUS="P" ISLAST="true"
path="2.4."
depth="2"/>

<snapshots purge_status="[null]" period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="6" created_at="2008-12-25 01:00:01.00" build_date="2008-12-25 01:00:01.00" version="1.0" project_id="5" scope="FIL" qualifier="CLA"
root_project_id="1" root_snapshot_id="2" parent_snapshot_id="4" STATUS="P" ISLAST="true"
path="2.4."
depth="2"/>


<!-- SNAPSHOT 5 : 500 lines and coverage 80.5% -->
<project_measures characteristic_id="[null]" id="1" metric_id="1" value="500" snapshot_id="5"
url="[null]" variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]" variation_value_4="[null]" variation_value_5="[null]"
rule_priority="[null]" alert_text="[null]" RULES_CATEGORY_ID="[null]"
RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
alert_status="[null]" description="[null]"/>

<project_measures characteristic_id="[null]" id="2" metric_id="2" value="80.5" snapshot_id="5"
url="[null]" variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]" variation_value_4="[null]" variation_value_5="[null]"
rule_priority="[null]" alert_text="[null]" RULES_CATEGORY_ID="[null]"
RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
alert_status="[null]" description="[null]"/>


<!-- SNAPSHOT 6 : 30 lines, coverage 20.6% and 10 duplicated lines -->
<project_measures characteristic_id="[null]" id="3" metric_id="1" value="30" snapshot_id="6"
url="[null]" variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]" variation_value_4="[null]" variation_value_5="[null]"
rule_priority="[null]" alert_text="[null]" RULES_CATEGORY_ID="[null]"
RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
alert_status="[null]" description="[null]"/>

<project_measures characteristic_id="[null]" id="4" metric_id="2" value="20.6" snapshot_id="6"
url="[null]" variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]" variation_value_4="[null]" variation_value_5="[null]"
rule_priority="[null]" alert_text="[null]" RULES_CATEGORY_ID="[null]"
RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
alert_status="[null]" description="[null]"/>

<project_measures characteristic_id="[null]" id="5" metric_id="3" value="10" snapshot_id="6"
url="[null]" variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]" variation_value_4="[null]" variation_value_5="[null]"
rule_priority="[null]" alert_text="[null]" RULES_CATEGORY_ID="[null]"
RULE_ID="[null]" text_value="[null]" tendency="[null]" measure_date="[null]" project_id="[null]"
alert_status="[null]" description="[null]"/>
</dataset>

+ 0
- 39
sonar-server/src/test/resources/org/sonar/server/filters/FilterExecutorTest/shared.xml View File

@@ -1,39 +0,0 @@
<dataset>
<projects long_name="java project" id="1" scope="PRJ" kee="project:java" qualifier="TRK" name="java project"
root_id="[null]"
description="[null]" enabled="true" language="java" copy_resource_id="[null]" person_id="[null]"/>

<projects long_name="php project" id="2" scope="PRJ" kee="project:a-php-project" qualifier="TRK" name="php project"
root_id="[null]"
description="[null]" enabled="true" language="php" copy_resource_id="[null]" person_id="[null]"/>

<projects long_name="org.sonar.foo" id="3" scope="DIR" kee="project:java:org.sonar.foo" qualifier="PAC" name="org.sonar.foo"
root_id="1"
description="[null]" enabled="true" language="java" copy_resource_id="[null]" person_id="[null]"/>


<!-- Java project -->
<snapshots purge_status="[null]" period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="1" created_at="2008-12-20 00:00:00.00" build_date="2008-12-20 00:00:00.00" version="1.0" project_id="1" scope="PRJ" qualifier="TRK"
root_project_id="1" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="P" ISLAST="false"
path=""
depth="0"/>

<snapshots purge_status="[null]" period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="2" created_at="2008-12-25 01:00:00.00" build_date="2008-12-25 01:00:00.00" version="1.0" project_id="1" scope="PRJ" qualifier="TRK"
root_project_id="1" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="P" ISLAST="true"
path=""
depth="0"/>

<!-- PHP project -->
<snapshots purge_status="[null]" period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="3" created_at="2008-12-31 02:00:00.00" build_date="2008-12-31 02:00:00.00" version="1.0" project_id="2" scope="PRJ" qualifier="TRK"
root_project_id="2" root_snapshot_id="[null]" parent_snapshot_id="[null]" STATUS="P" ISLAST="true"
path=""
depth="0"/>

<!-- Java package -->
<snapshots purge_status="[null]" period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="4" created_at="2008-12-25 01:00:01.00" build_date="2008-12-25 01:00:01.00" version="1.0" project_id="3" scope="DIR" qualifier="PAC"
root_project_id="1" root_snapshot_id="2" parent_snapshot_id="2" STATUS="P" ISLAST="true"
path="2."
depth="1"/>

</dataset>

Loading…
Cancel
Save