--- /dev/null
+/*
+ * 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.collect.Lists;
+import com.google.common.collect.Sets;
+import org.sonar.api.measures.Metric;
+
+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 String resourceName;
+ private Date fromDate = null, toDate = null;
+ private boolean userFavourites = false;
+
+ // conditions on measures
+ private List<MeasureFilterValueCondition> measureConditions = Lists.newArrayList();
+
+ // sort
+ private MeasureFilterSort sort = new MeasureFilterSort();
+
+ public String baseResourceKey() {
+ return baseResourceKey;
+ }
+
+ public MeasureFilter setBaseResourceKey(String s) {
+ this.baseResourceKey = s;
+ return this;
+ }
+
+ public MeasureFilter setOnBaseResourceChildren(boolean b) {
+ this.onBaseResourceChildren = b;
+ return this;
+ }
+
+ public boolean isOnBaseResourceChildren() {
+ return onBaseResourceChildren;
+ }
+
+ public MeasureFilter setResourceScopes(Set<String> resourceScopes) {
+ this.resourceScopes = resourceScopes;
+ return this;
+ }
+
+ public MeasureFilter setResourceQualifiers(String... qualifiers) {
+ this.resourceQualifiers = Sets.newHashSet(qualifiers);
+ return this;
+ }
+
+ public MeasureFilter setResourceLanguages(String... languages) {
+ this.resourceLanguages = Sets.newHashSet(languages);
+ return this;
+ }
+
+ public MeasureFilter setUserFavourites(boolean b) {
+ this.userFavourites = b;
+ return this;
+ }
+
+ public boolean userFavourites() {
+ return userFavourites;
+ }
+
+ public String resourceName() {
+ return resourceName;
+ }
+
+ public MeasureFilter setResourceName(String s) {
+ this.resourceName = s;
+ return this;
+ }
+
+ public MeasureFilter addCondition(MeasureFilterValueCondition condition) {
+ this.measureConditions.add(condition);
+ return this;
+ }
+
+ public MeasureFilter setSortOn(MeasureFilterSort.Field sortField) {
+ this.sort.setField(sortField);
+ return this;
+ }
+
+ public MeasureFilter setSortAsc(boolean b) {
+ this.sort.setAsc(b);
+ return this;
+ }
+
+ public MeasureFilter setSortOnMetric(Metric m) {
+ this.sort.setMetric(m);
+ return this;
+ }
+
+ public MeasureFilter setSortOnPeriod(int period) {
+ this.sort.setPeriod(period);
+ return this;
+ }
+
+ public MeasureFilter setFromDate(Date d) {
+ this.fromDate = d;
+ return this;
+ }
+
+ public MeasureFilter setToDate(Date d) {
+ this.toDate = d;
+ return this;
+ }
+
+ public Date fromDate() {
+ return fromDate;
+ }
+
+ public Date toDate() {
+ return toDate;
+ }
+
+ public Set<String> resourceScopes() {
+ return resourceScopes;
+ }
+
+ public Set<String> resourceQualifiers() {
+ return resourceQualifiers;
+ }
+
+ public Set<String> resourceLanguages() {
+ return resourceLanguages;
+ }
+
+ public List<MeasureFilterValueCondition> measureConditions() {
+ return measureConditions;
+ }
+
+ MeasureFilterSort sort() {
+ return sort;
+ }
+
+}
--- /dev/null
+/*
+ * 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.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.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;
+ private ResourceDao resourceDao;
+
+ public MeasureFilterExecutor(MyBatis mybatis, Database database, ResourceDao resourceDao) {
+ this.mybatis = mybatis;
+ this.database = database;
+ this.resourceDao = resourceDao;
+ }
+
+ public List<MeasureFilterRow> execute(MeasureFilter filter, @Nullable Long userId) {
+ List<MeasureFilterRow> rows;
+ SqlSession session = null;
+ try {
+ session = mybatis.openSession();
+ MesasureFilterContext context = prepareContext(filter, userId, session);
+
+ if (isValid(filter, context)) {
+ MeasureFilterSql sql = new MeasureFilterSql(database, filter, context);
+ Connection connection = session.getConnection();
+ rows = sql.execute(connection);
+ } else {
+ rows = Collections.emptyList();
+ }
+
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+
+ } finally {
+ MyBatis.closeQuietly(session);
+ }
+
+ 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));
+ }
+ return context;
+ }
+
+ static boolean isValid(MeasureFilter filter, MesasureFilterContext context) {
+ return
+ !(filter.resourceQualifiers().isEmpty() && !filter.userFavourites()) &&
+ !(filter.isOnBaseResourceChildren() && context.getBaseSnapshot() == null) &&
+ !(filter.userFavourites() && context.getUserId() == null);
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+public class MeasureFilterRow {
+ private final long snapshotId;
+ private final long resourceId;
+ private final long resourceRootId;
+ private String sortText;
+
+ MeasureFilterRow(long snapshotId, long resourceId, long resourceRootId) {
+ this.snapshotId = snapshotId;
+ this.resourceId = resourceId;
+ this.resourceRootId = resourceRootId;
+ }
+
+ public long getSnapshotId() {
+ return snapshotId;
+ }
+
+ public long getResourceId() {
+ return resourceId;
+ }
+
+ public long getResourceRootId() {
+ return resourceRootId;
+ }
+
+ public String getSortText() {
+ return sortText;
+ }
+
+ MeasureFilterRow setSortText(String s) {
+ this.sortText = s;
+ return this;
+ }
+}
--- /dev/null
+/*
+ * 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.sonar.api.measures.Metric;
+
+class MeasureFilterSort {
+ public static enum Field {
+ KEY, NAME, VERSION, LANGUAGE, DATE, METRIC
+ }
+
+ private Field field = Field.NAME;
+ private Metric metric = null;
+ private int period = -1;
+ private boolean asc = true;
+
+ MeasureFilterSort() {
+ }
+
+ void setField(Field field) {
+ this.field = field;
+ }
+
+ void setMetric(Metric metric) {
+ this.field = Field.METRIC;
+ this.metric = metric;
+ }
+
+ void setPeriod(int period) {
+ this.period = (period > 0 ? period : -1);
+ }
+
+ void setAsc(boolean asc) {
+ this.asc = asc;
+ }
+
+ public Field field() {
+ return field;
+ }
+
+ boolean onMeasures() {
+ return field == Field.METRIC;
+ }
+
+ Metric metric() {
+ return metric;
+ }
+
+ boolean isSortedByDatabase() {
+ return metric != null && metric.isNumericType();
+ }
+
+ boolean isAsc() {
+ return asc;
+ }
+
+ String column() {
+ // only numeric metrics can be sorted by database, else results are sorted programmatically.
+ String column = null;
+ switch (field) {
+ case KEY:
+ column = "p.kee";
+ break;
+ case NAME:
+ column = "p.long_name";
+ break;
+ case VERSION:
+ column = "s.version";
+ break;
+ case LANGUAGE:
+ column = "p.language";
+ break;
+ case DATE:
+ column = "s.created_at";
+ break;
+ case METRIC:
+ if (metric.isNumericType()) {
+ column = (period > 0 ? "pm.variation_value_" + period : "pm.value");
+ } else {
+ column = "pm.text_value";
+ }
+ break;
+ }
+ return column;
+ }
+
+}
--- /dev/null
+/*
+ * 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.base.Function;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Ordering;
+import org.apache.commons.lang.StringEscapeUtils;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.measures.Metric;
+import org.sonar.core.persistence.Database;
+import org.sonar.core.persistence.dialect.PostgreSql;
+import org.sonar.core.resource.SnapshotDto;
+
+import javax.annotation.Nullable;
+import java.sql.*;
+import java.util.Collection;
+import java.util.List;
+
+class MeasureFilterSql {
+
+ private final Database database;
+ private final MeasureFilter filter;
+ private final MesasureFilterContext context;
+ private final StringBuilder sql = new StringBuilder(1000);
+ private final List<Date> dateParameters = Lists.newArrayList();
+
+ MeasureFilterSql(Database database, MeasureFilter filter, MesasureFilterContext context) {
+ this.database = database;
+ this.filter = filter;
+ this.context = context;
+ init();
+ }
+
+ List<MeasureFilterRow> execute(Connection connection) throws SQLException {
+ PreparedStatement statement = connection.prepareStatement(sql.toString());
+ ResultSet rs = null;
+ try {
+ for (int index = 0; index < dateParameters.size(); index++) {
+ statement.setDate(index + 1, dateParameters.get(index));
+ }
+ rs = statement.executeQuery();
+ return process(rs);
+
+ } finally {
+ closeQuietly(statement, rs);
+ }
+ }
+
+ String sql() {
+ return sql.toString();
+ }
+
+ 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++) {
+ 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);
+ sql.append(" UNION ");
+ appendConditionBlock(index, condition);
+ }
+
+ sql.append(") block GROUP BY block.id");
+ if (!filter.measureConditions().isEmpty()) {
+ sql.append(" HAVING ");
+ for (int index = 0; index < filter.measureConditions().size(); index++) {
+ if (index > 0) {
+ sql.append(" AND ");
+ }
+ sql.append(" max(crit_").append(index).append(") IS NOT NULL ");
+ }
+ }
+ if (filter.sort().isSortedByDatabase()) {
+ sql.append(" ORDER BY sortval");
+ sql.append(filter.sort().isAsc() ? " ASC " : " DESC ");
+ }
+ }
+
+ 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);
+ 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 ");
+ if (filter.sort().onMeasures()) {
+ sql.append(" LEFT OUTER JOIN project_measures pm ON s.id=pm.snapshot_id AND pm.metric_id=");
+ sql.append(filter.sort().metric().getId());
+ 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 ");
+ }
+ sql.append(" WHERE ");
+ appendResourceConditions();
+ }
+
+ private void appendConditionBlock(int conditionIndex, MeasureFilterValueCondition 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++) {
+ sql.append(", ");
+ if (j == conditionIndex) {
+ sql.append(condition.valueColumn());
+ } else {
+ sql.append(nullSelect(filter.measureConditions().get(j).metric()));
+ }
+ sql.append(" crit_").append(j);
+ }
+ sql.append(" FROM snapshots s INNER JOIN projects p ON s.project_id=p.id INNER JOIN project_measures pm ON s.id=pm.snapshot_id ");
+ 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);
+ }
+
+ 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()) {
+ sql.append(" AND s.qualifier IN ");
+ appendInStatement(filter.resourceQualifiers(), sql);
+ }
+ if (!filter.resourceScopes().isEmpty()) {
+ sql.append(" AND s.scope IN ");
+ appendInStatement(filter.resourceScopes(), sql);
+ }
+ if (!filter.resourceLanguages().isEmpty()) {
+ sql.append(" AND p.language IN ");
+ appendInStatement(filter.resourceLanguages(), sql);
+ }
+ if (filter.fromDate() != null) {
+ sql.append(" AND s.created_at >= ? ");
+ dateParameters.add(new java.sql.Date(filter.fromDate().getTime()));
+ }
+ if (filter.toDate() != null) {
+ sql.append(" AND s.created_at <= ? ");
+ dateParameters.add(new java.sql.Date(filter.toDate().getTime()));
+ }
+ if (filter.userFavourites() && 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())) {
+ 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("%'");
+ if (!filter.resourceQualifiers().isEmpty()) {
+ sql.append(" AND rindex.qualifier IN ");
+ appendInStatement(filter.resourceQualifiers(), sql);
+ }
+ sql.append(") ");
+ }
+ SnapshotDto baseSnapshot = context.getBaseSnapshot();
+ if (baseSnapshot != null) {
+ if (filter.isOnBaseResourceChildren()) {
+ sql.append(" AND s.parent_snapshot_id=").append(baseSnapshot.getId());
+ } 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(".%'");
+ }
+ }
+ }
+
+ List<MeasureFilterRow> process(ResultSet rs) throws SQLException {
+ List<MeasureFilterRow> rows = Lists.newArrayList();
+ boolean sortTextValues = !filter.sort().isSortedByDatabase();
+ while (rs.next()) {
+ MeasureFilterRow row = new MeasureFilterRow(rs.getLong(1), rs.getLong(2), rs.getLong(3));
+ if (sortTextValues) {
+ row.setSortText(rs.getString(4));
+ }
+ rows.add(row);
+ }
+ if (sortTextValues) {
+ // database does not manage case-insensitive text sorting. It must be done programmatically
+ Function<MeasureFilterRow, String> function = new Function<MeasureFilterRow, String>() {
+ public String apply(@Nullable MeasureFilterRow row) {
+ return (row != null ? StringUtils.defaultString(row.getSortText()) : "");
+ }
+ };
+ Ordering<MeasureFilterRow> ordering = Ordering.from(String.CASE_INSENSITIVE_ORDER).onResultOf(function).nullsFirst();
+ if (!filter.sort().isAsc()) {
+ ordering = ordering.reverse();
+ }
+ rows = ordering.sortedCopy(rows);
+ }
+ return rows;
+ }
+
+ private String nullSelect(Metric metric) {
+ if (metric.isNumericType() && PostgreSql.ID.equals(database.getDialect().getId())) {
+ return "null::integer";
+ }
+ return "null";
+ }
+
+
+ private static void appendInStatement(Collection<String> values, StringBuilder to) {
+ to.append(" ('");
+ to.append(StringUtils.join(values, "','"));
+ to.append("') ");
+ }
+
+ private static void closeQuietly(@Nullable Statement stmt, @Nullable ResultSet rs) {
+ if (rs != null) {
+ try {
+ rs.close();
+ } catch (SQLException e) {
+ LoggerFactory.getLogger(MeasureFilterSql.class).warn("Fail to close result set", e);
+ // ignore
+ }
+ }
+ if (stmt != null) {
+ try {
+ stmt.close();
+ } catch (SQLException e) {
+ LoggerFactory.getLogger(MeasureFilterSql.class).warn("Fail to close statement", e);
+ // ignore
+ }
+ }
+
+ }
+}
--- /dev/null
+/*
+ * 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.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;
+ }
+ }
+
+ private final Metric metric;
+ private final Operator operator;
+ private final float value;
+ private int period = -1;
+
+ public MeasureFilterValueCondition(Metric metric, Operator operator, float value) {
+ this.metric = metric;
+ this.operator = operator;
+ this.value = value;
+ }
+
+ public MeasureFilterValueCondition setPeriod(int period) {
+ this.period = (period > 0 ? period : -1);
+ return this;
+ }
+
+ public Metric metric() {
+ return metric;
+ }
+
+ public Operator operator() {
+ return operator;
+ }
+
+ public double value() {
+ return value;
+ }
+
+ public int period() {
+ return period;
+ }
+
+ String valueColumn() {
+ if (period > 0) {
+ return "pm.variation_value_" + period;
+ }
+ return "pm.value";
+ }
+
+ void appendSql(StringBuilder sql) {
+ sql.append(" pm.metric_id=");
+ sql.append(metric.getId());
+ sql.append(" AND ").append(valueColumn()).append(operator.getSql()).append(value);
+ }
+}
--- /dev/null
+/*
+ * 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.sonar.core.resource.SnapshotDto;
+
+public class MesasureFilterContext {
+ private Long userId;
+ private SnapshotDto baseSnapshot;
+
+ public Long getUserId() {
+ return userId;
+ }
+
+ public void setUserId(Long userId) {
+ this.userId = userId;
+ }
+
+ public SnapshotDto getBaseSnapshot() {
+ return baseSnapshot;
+ }
+
+ public void setBaseSnapshot(SnapshotDto baseSnapshot) {
+ this.baseSnapshot = baseSnapshot;
+ }
+}
return session.getMapper(ResourceMapper.class).selectResource(projectId);
}
+ public SnapshotDto getLastSnapshot(String resourceKey, SqlSession session) {
+ return session.getMapper(ResourceMapper.class).selectLastSnapshotByKey(resourceKey);
+ }
+
public List<ResourceDto> getDescendantProjects(long projectId) {
SqlSession session = mybatis.openSession();
try {
return resources;
}
+
private void appendChildProjects(long projectId, ResourceMapper mapper, List<ResourceDto> resources) {
List<ResourceDto> subProjects = mapper.selectDescendantProjects(projectId);
for (ResourceDto subProject : subProjects) {
public interface ResourceMapper {
SnapshotDto selectSnapshot(Long snapshotId);
+ SnapshotDto selectLastSnapshotByKey(String resourceKey);
+
ResourceDto selectResource(long id);
List<ResourceDto> selectDescendantProjects(long rootProjectId);
select * from snapshots where id=#{id}
</select>
+ <select id="selectLastSnapshotByKey" parameterType="string" resultMap="snapshotResultMap">
+ select s.* from snapshots s, projects p where p.kee=#{id} and p.enabled=${_true} and p.copy_resource_id is null and s.islast=${_true} and p.id=s.project_id
+ </select>
+
<select id="selectDescendantProjects" parameterType="long" resultMap="resourceResultMap">
select * from projects where scope='PRJ' and root_id=#{id}
</select>
--- /dev/null
+/*
+ * 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.junit.Before;
+import org.junit.Test;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.core.persistence.AbstractDaoTestCase;
+import org.sonar.core.resource.ResourceDao;
+import org.sonar.core.resource.SnapshotDto;
+
+import java.util.List;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class MeasureFilterExecutorTest extends AbstractDaoTestCase {
+ private static final long JAVA_PROJECT_ID = 1L;
+ private static final long JAVA_FILE_BIG_ID = 3L;
+ private static final long JAVA_FILE_TINY_ID = 4L;
+ private static final long JAVA_PROJECT_SNAPSHOT_ID = 101L;
+ private static final long JAVA_FILE_BIG_SNAPSHOT_ID = 103L;
+ private static final long JAVA_FILE_TINY_SNAPSHOT_ID = 104L;
+ private static final long JAVA_PACKAGE_SNAPSHOT_ID = 102L;
+ private static final long PHP_PROJECT_ID = 10L;
+ private static final long PHP_SNAPSHOT_ID = 110L;
+ private static final Metric METRIC_LINES = new Metric.Builder("lines", "Lines", Metric.ValueType.INT).create().setId(1);
+ private static final Metric METRIC_PROFILE = new Metric.Builder("profile", "Profile", Metric.ValueType.STRING).create().setId(2);
+ private static final Metric METRIC_COVERAGE = new Metric.Builder("coverage", "Coverage", Metric.ValueType.FLOAT).create().setId(3);
+
+ private MeasureFilterExecutor executor;
+
+ @Before
+ public void before() {
+ executor = new MeasureFilterExecutor(getMyBatis(), getDatabase(), new ResourceDao(getMyBatis()));
+ setupData("shared");
+ }
+
+ @Test
+ public void invalid_filter_should_not_return_results() {
+ MeasureFilter filter = new MeasureFilter();
+ // no qualifiers
+ assertThat(executor.execute(filter, null)).isEmpty();
+ }
+
+ @Test
+ public void filter_is_not_valid_if_missing_base_snapshot() {
+ MesasureFilterContext context = new MesasureFilterContext();
+ MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setOnBaseResourceChildren(true);
+ assertThat(MeasureFilterExecutor.isValid(filter, context)).isFalse();
+
+ context.setBaseSnapshot(new SnapshotDto().setId(123L));
+ assertThat(MeasureFilterExecutor.isValid(filter, context)).isTrue();
+ }
+
+ @Test
+ public void filter_is_not_valid_if_anonymous_favourites() {
+ MesasureFilterContext context = new MesasureFilterContext();
+ MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setUserFavourites(true);
+ assertThat(MeasureFilterExecutor.isValid(filter, context)).isFalse();
+
+ context.setUserId(123L);
+ assertThat(MeasureFilterExecutor.isValid(filter, context)).isTrue();
+ }
+
+ @Test
+ public void projects_without_measure_conditions() {
+ MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setSortOn(MeasureFilterSort.Field.LANGUAGE);
+ List<MeasureFilterRow> rows = executor.execute(filter, null);
+
+ assertThat(rows).hasSize(2);
+ verifyJavaProject(rows.get(0));
+ verifyPhpProject(rows.get(1));
+ }
+
+ @Test
+ public void test_default_sort() {
+ MeasureFilter filter = new MeasureFilter().setResourceQualifiers("CLA");
+
+ assertThat(filter.sort().isAsc()).isTrue();
+ assertThat(filter.sort().field()).isEqualTo(MeasureFilterSort.Field.NAME);
+ assertThat(filter.sort().metric()).isNull();
+ }
+
+ @Test
+ public void sort_by_ascending_resource_name() {
+ MeasureFilter filter = new MeasureFilter().setResourceQualifiers("CLA").setSortAsc(true);
+ List<MeasureFilterRow> rows = executor.execute(filter, null);
+
+ // Big -> Tiny
+ assertThat(rows).hasSize(2);
+ verifyJavaBigFile(rows.get(0));
+ verifyJavaTinyFile(rows.get(1));
+ }
+
+ @Test
+ public void sort_by_descending_resource_name() {
+ MeasureFilter filter = new MeasureFilter().setResourceQualifiers("CLA").setSortAsc(false);
+ List<MeasureFilterRow> rows = executor.execute(filter, null);
+
+ // Tiny -> Big
+ assertThat(rows).hasSize(2);
+ verifyJavaTinyFile(rows.get(0));
+ verifyJavaBigFile(rows.get(1));
+ }
+
+ @Test
+ public void sort_by_ascending_text_measure() {
+ MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setSortOnMetric(METRIC_PROFILE);
+ List<MeasureFilterRow> rows = executor.execute(filter, null);
+
+ assertThat(rows).hasSize(2);
+ verifyPhpProject(rows.get(0));//php way
+ verifyJavaProject(rows.get(1));// Sonar way
+ }
+
+ @Test
+ public void sort_by_descending_text_measure() {
+ MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setSortOnMetric(METRIC_PROFILE).setSortAsc(false);
+ List<MeasureFilterRow> rows = executor.execute(filter, null);
+
+ assertThat(rows).hasSize(2);
+ verifyJavaProject(rows.get(0));// Sonar way
+ verifyPhpProject(rows.get(1));//php way
+ }
+
+ @Test
+ public void sort_by_missing_text_measure() {
+ // the metric 'profile' is not set on files
+ MeasureFilter filter = new MeasureFilter().setResourceQualifiers("CLA").setSortOnMetric(METRIC_PROFILE);
+ List<MeasureFilterRow> rows = executor.execute(filter, null);
+
+ assertThat(rows).hasSize(2);//2 files randomly sorted
+ }
+
+ @Test
+ public void sort_by_ascending_numeric_measure() {
+ MeasureFilter filter = new MeasureFilter().setResourceQualifiers("CLA").setSortOnMetric(METRIC_LINES);
+ List<MeasureFilterRow> rows = executor.execute(filter, null);
+
+ // Tiny -> Big
+ assertThat(rows).hasSize(2);
+ verifyJavaTinyFile(rows.get(0));
+ verifyJavaBigFile(rows.get(1));
+ }
+
+ @Test
+ public void sort_by_descending_numeric_measure() {
+ MeasureFilter filter = new MeasureFilter().setResourceQualifiers("CLA").setSortOnMetric(METRIC_LINES).setSortAsc(false);
+ List<MeasureFilterRow> rows = executor.execute(filter, null);
+
+ // Big -> Tiny
+ assertThat(rows).hasSize(2);
+ verifyJavaBigFile(rows.get(0));
+ verifyJavaTinyFile(rows.get(1));
+ }
+
+ @Test
+ public void sort_by_missing_numeric_measure() {
+ // coverage measures are not computed
+ MeasureFilter filter = new MeasureFilter().setResourceQualifiers("CLA").setSortOnMetric(METRIC_COVERAGE);
+ List<MeasureFilterRow> rows = executor.execute(filter, null);
+
+ // 2 files, random order
+ assertThat(rows).hasSize(2);
+ }
+
+ @Test
+ public void sort_by_ascending_variation() {
+ MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setSortOnMetric(METRIC_LINES).setSortOnPeriod(5);
+ List<MeasureFilterRow> rows = executor.execute(filter, null);
+
+ assertThat(rows).hasSize(2);
+ verifyJavaProject(rows.get(0));// +400
+ verifyPhpProject(rows.get(1));// +4900
+ }
+
+ @Test
+ public void sort_by_descending_variation() {
+ MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK")
+ .setSortOnMetric(METRIC_LINES).setSortOnPeriod(5).setSortAsc(false);
+ List<MeasureFilterRow> rows = executor.execute(filter, null);
+
+ assertThat(rows).hasSize(2);
+ verifyPhpProject(rows.get(0));// +4900
+ verifyJavaProject(rows.get(1));// +400
+ }
+
+ @Test
+ public void sort_by_ascending_date() {
+ MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setSortOn(MeasureFilterSort.Field.DATE);
+ List<MeasureFilterRow> rows = executor.execute(filter, null);
+
+ verifyJavaProject(rows.get(0));// 2008
+ verifyPhpProject(rows.get(1));// 2012
+ }
+
+ @Test
+ public void sort_by_descending_date() {
+ MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setSortOn(MeasureFilterSort.Field.DATE).setSortAsc(false);
+ List<MeasureFilterRow> rows = executor.execute(filter, null);
+
+ verifyPhpProject(rows.get(0));// 2012
+ verifyJavaProject(rows.get(1));// 2008
+ }
+
+ @Test
+ public void condition_on_numeric_measure() {
+ 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);
+
+ assertThat(rows).hasSize(1);
+ verifyJavaBigFile(rows.get(0));
+ }
+
+ @Test
+ public void condition_on_measure_variation() {
+ 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);
+
+ assertThat(rows).hasSize(1);
+ verifyPhpProject(rows.get(0));
+ }
+
+ @Test
+ public void multiple_conditions_on_numeric_measures() {
+ 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);
+
+ assertThat(rows).hasSize(1);
+ verifyJavaTinyFile(rows.get(0));
+ }
+
+ @Test
+ public void filter_by_language() {
+ MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setResourceLanguages("java", "cobol");
+ List<MeasureFilterRow> rows = executor.execute(filter, null);
+
+ assertThat(rows).hasSize(1);
+ verifyJavaProject(rows.get(0));
+ }
+
+ @Test
+ public void filter_by_min_date() {
+ MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setFromDate(DateUtils.parseDate("2012-12-13"));
+ List<MeasureFilterRow> rows = executor.execute(filter, null);
+
+ // php has been analyzed in 2012-12-13, whereas java project has been analyzed in 2008
+ assertThat(rows).hasSize(1);
+ verifyPhpProject(rows.get(0));
+ }
+
+ @Test
+ public void filter_by_range_of_dates() {
+ 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);
+
+ // php has been analyzed in 2012-12-13, whereas java project has been analyzed in 2008
+ assertThat(rows).hasSize(1);
+ verifyJavaProject(rows.get(0));
+ }
+
+ @Test
+ public void filter_by_resource_name() {
+ MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK").setResourceName("PHP Proj");
+ List<MeasureFilterRow> rows = executor.execute(filter, null);
+
+ assertThat(rows).hasSize(1);
+ verifyPhpProject(rows.get(0));
+ }
+
+ @Test
+ public void filter_by_base_resource() {
+ MeasureFilter filter = new MeasureFilter().setResourceQualifiers("CLA").setBaseResourceKey("java_project");
+ List<MeasureFilterRow> rows = executor.execute(filter, null);
+
+ assertThat(rows).hasSize(2);
+ // default sort is on resource name
+ verifyJavaBigFile(rows.get(0));
+ verifyJavaTinyFile(rows.get(1));
+ }
+
+ @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);
+
+ 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() {
+ MeasureFilter filter = new MeasureFilter().setResourceQualifiers("TRK", "PAC", "CLA").setBaseResourceKey("java_project:org.sonar.foo.Big").setOnBaseResourceChildren(true);
+ List<MeasureFilterRow> rows = executor.execute(filter, null);
+
+ assertThat(rows).isEmpty();
+ }
+
+ @Test
+ public void filter_by_user_favourites() {
+ MeasureFilter filter = new MeasureFilter().setUserFavourites(true);
+ List<MeasureFilterRow> rows = executor.execute(filter, 50L);
+
+ assertThat(rows).hasSize(2);
+ verifyJavaBigFile(rows.get(0));
+ verifyPhpProject(rows.get(1));
+ }
+
+ private void verifyJavaProject(MeasureFilterRow row) {
+ assertThat(row.getSnapshotId()).isEqualTo(JAVA_PROJECT_SNAPSHOT_ID);
+ assertThat(row.getResourceId()).isEqualTo(JAVA_PROJECT_ID);
+ assertThat(row.getResourceRootId()).isEqualTo(JAVA_PROJECT_ID);
+ }
+
+ private void verifyJavaBigFile(MeasureFilterRow row) {
+ assertThat(row.getSnapshotId()).isEqualTo(JAVA_FILE_BIG_SNAPSHOT_ID);
+ assertThat(row.getResourceId()).isEqualTo(JAVA_FILE_BIG_ID);
+ assertThat(row.getResourceRootId()).isEqualTo(JAVA_PROJECT_ID);
+ }
+
+ private void verifyJavaTinyFile(MeasureFilterRow row) {
+ assertThat(row.getSnapshotId()).isEqualTo(JAVA_FILE_TINY_SNAPSHOT_ID);
+ assertThat(row.getResourceId()).isEqualTo(JAVA_FILE_TINY_ID);
+ assertThat(row.getResourceRootId()).isEqualTo(JAVA_PROJECT_ID);
+ }
+
+ private void verifyPhpProject(MeasureFilterRow row) {
+ assertThat(row.getSnapshotId()).isEqualTo(PHP_SNAPSHOT_ID);
+ assertThat(row.getResourceId()).isEqualTo(PHP_PROJECT_ID);
+ assertThat(row.getResourceRootId()).isEqualTo(PHP_PROJECT_ID);
+ }
+}
*/
package org.sonar.core.persistence;
-import org.dbunit.ext.mssql.InsertIdentityOperation;
-import org.dbunit.operation.DatabaseOperation;
-
import com.google.common.collect.Maps;
import com.google.common.io.Closeables;
import org.apache.commons.io.IOUtils;
import org.dbunit.dataset.*;
import org.dbunit.dataset.filter.DefaultColumnFilter;
import org.dbunit.dataset.xml.FlatXmlDataSet;
+import org.dbunit.ext.mssql.InsertIdentityOperation;
+import org.dbunit.operation.DatabaseOperation;
import org.junit.Assert;
import org.junit.Before;
import org.sonar.api.config.Settings;
return myBatis;
}
+ protected Database getDatabase() {
+ return database;
+ }
+
protected void setupData(String... testNames) {
InputStream[] streams = new InputStream[testNames.length];
try {
--- /dev/null
+<dataset>
+ <metrics 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"
+ delete_historical_data="[null]"/>
+
+ <metrics id="2" name="profile" val_type="STRING" description="Profile" domain="Rules"
+ short_name="Profile" qualitative="false" user_managed="false" enabled="true" origin="JAV"
+ worst_value="[null]"
+ optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"
+ delete_historical_data="[null]"/>
+
+ <metrics id="3" name="coverage" val_type="FLOAT" description="Coverage" domain="Test"
+ short_name="Coverage" qualitative="true" user_managed="false" enabled="true" origin="JAV"
+ worst_value="[null]"
+ optimized_best_value="true" best_value="100" direction="1" hidden="false"
+ delete_historical_data="[null]"/>
+
+ <!-- java project -->
+ <projects kee="java_project" long_name="Java project" scope="PRJ" qualifier="TRK" name="Java project"
+ id="1" root_id="[null]"
+ description="[null]" enabled="true" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <projects kee="java_project:org.sonar.foo" scope="DIR" qualifier="PAC" long_name="org.sonar.foo" name="org.sonar.foo"
+ id="2" root_id="1"
+ description="[null]" enabled="true" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <projects kee="java_project:org.sonar.foo.Big" scope="FIL" qualifier="CLA" long_name="org.sonar.foo.Big"
+ name="Big"
+ id="3" root_id="1"
+ description="[null]" enabled="true" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <projects kee="java_project:org.sonar.foo.Tiny" scope="FIL" qualifier="CLA" long_name="org.sonar.foo.Tiny" name="Tiny"
+ id="4" root_id="1"
+ description="[null]" enabled="true" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+ <snapshots id="101" project_id="1" root_project_id="1" root_snapshot_id="[null]" parent_snapshot_id="[null]"
+ scope="PRJ" qualifier="TRK" path="" depth="0"
+ 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]"
+ created_at="2008-12-20 00:00:00.00" build_date="2008-12-20 00:00:00.00"
+ version="1.0" status="P" islast="true"/>
+
+ <snapshots id="102" project_id="2" root_project_id="1" root_snapshot_id="101" parent_snapshot_id="101"
+ scope="DIR" qualifier="PAC" path="101." depth="1"
+ 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]"
+ created_at="2008-12-20 00:00:00.00" build_date="2008-12-20 00:00:00.00"
+ version="1.0" status="P" islast="true"/>
+
+ <snapshots id="103" project_id="3" root_project_id="1" root_snapshot_id="101" parent_snapshot_id="102"
+ scope="FIL" qualifier="CLA" path="101.102." depth="2"
+ 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]"
+ created_at="2008-12-20 00:00:00.00" build_date="2008-12-20 00:00:00.00"
+ version="1.0" status="P" islast="true"/>
+
+ <snapshots id="104" project_id="4" root_project_id="1" root_snapshot_id="101" parent_snapshot_id="102"
+ scope="FIL" qualifier="CLA" path="101.102." depth="2"
+ 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]"
+ created_at="2008-12-20 00:00:00.00" build_date="2008-12-20 00:00:00.00"
+ version="1.0" status="P" islast="true"/>
+
+
+ <!-- lines, variation during period 5 -->
+ <project_measures id="1001" metric_id="1" value="510" snapshot_id="101"
+ url="[null]" variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]"
+ variation_value_4="[null]" variation_value_5="400"
+ 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]" characteristic_id="[null]"/>
+
+ <project_measures id="1002" metric_id="1" value="510" snapshot_id="102"
+ 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]" characteristic_id="[null]"/>
+
+ <project_measures id="1003" metric_id="1" value="500" snapshot_id="103"
+ 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]" characteristic_id="[null]"/>
+
+ <project_measures id="1004" metric_id="1" value="10" snapshot_id="104"
+ 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]" characteristic_id="[null]"/>
+
+ <!-- profile of java project -->
+ <project_measures id="1005" metric_id="2" value="[null]" snapshot_id="101"
+ 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="Sonar way" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]" characteristic_id="[null]"/>
+
+ <!-- php project -->
+ <projects kee="php_project" long_name="PHP project" scope="PRJ" qualifier="TRK" name="PHP project"
+ id="10" root_id="[null]"
+ description="[null]" enabled="true" language="php" copy_resource_id="[null]" person_id="[null]"/>
+
+
+ <snapshots id="110" project_id="10" root_project_id="10" root_snapshot_id="[null]" parent_snapshot_id="[null]"
+ scope="PRJ" qualifier="TRK" path="" depth="0"
+ 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]"
+ created_at="2012-12-13 04:06:00.00" build_date="2012-12-13 04:06:00.00"
+ version="3.0" status="P" islast="true"/>
+
+ <!-- lines, many new lines during period 5 -->
+ <project_measures id="1010" metric_id="1" value="5000" snapshot_id="110"
+ url="[null]" variation_value_1="[null]" variation_value_2="[null]" variation_value_3="[null]"
+ variation_value_4="[null]" variation_value_5="4900"
+ 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]" characteristic_id="[null]"/>
+
+ <project_measures id="1011" metric_id="2" value="[null]" snapshot_id="110"
+ 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="php way" tendency="[null]" measure_date="[null]" project_id="[null]"
+ alert_status="[null]" description="[null]" characteristic_id="[null]"/>
+
+
+ <resource_index kee="java project" position="0" name_size="12" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee="java projec" position="1" name_size="12" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee="java proje" position="2" name_size="12" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <resource_index kee="java proj" position="3" name_size="12" resource_id="1" root_project_id="1" qualifier="TRK"/>
+ <!-- etc -->
+ <resource_index kee="php project" position="0" name_size="11" resource_id="10" root_project_id="10" qualifier="TRK"/>
+ <resource_index kee="php projec" position="1" name_size="11" resource_id="10" root_project_id="10" qualifier="TRK"/>
+ <resource_index kee="php proje" position="2" name_size="11" resource_id="10" root_project_id="10" qualifier="TRK"/>
+ <resource_index kee="php proj" position="3" name_size="11" resource_id="10" root_project_id="10" qualifier="TRK"/>
+ <!-- etc -->
+
+
+ <!-- two favourites : Big.java and PHP project -->
+ <properties prop_key="favourite" resource_id="3" text_value="[null]" user_id="50"/>
+ <properties prop_key="favourite" resource_id="10" text_value="[null]" user_id="50"/>
+
+ <!-- another properties -->
+ <properties prop_key="favourite" resource_id="1" text_value="[null]" user_id="1234"/>
+ <properties prop_key="sonar.profile" resource_id="1" text_value="Sonar way" user_id="[null]"/>
+
+</dataset>
\ No newline at end of file