001/* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2008-2012 SonarSource 004 * mailto:contact AT sonarsource DOT com 005 * 006 * Sonar is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * Sonar is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with Sonar; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 019 */ 020package org.sonar.server.filters; 021 022import com.google.common.annotations.VisibleForTesting; 023import org.apache.commons.lang.StringUtils; 024import org.slf4j.Logger; 025import org.slf4j.LoggerFactory; 026import org.sonar.api.ServerComponent; 027import org.sonar.api.database.DatabaseSession; 028import org.sonar.api.database.model.Snapshot; 029import org.sonar.api.utils.SonarException; 030import org.sonar.api.utils.TimeProfiler; 031import org.sonar.core.persistence.Database; 032import org.sonar.core.persistence.dialect.Dialect; 033import org.sonar.core.persistence.dialect.MsSql; 034 035import javax.persistence.Query; 036 037import java.util.Collections; 038 039public class FilterExecutor implements ServerComponent { 040 private static final Logger LOG = LoggerFactory.getLogger(FilterExecutor.class); 041 private static final int SQL_INITIAL_SIZE = 1000; 042 private DatabaseSession session; 043 private Dialect dialect; 044 045 public FilterExecutor(DatabaseSession session, Database database) { 046 this(session, database.getDialect()); 047 } 048 049 @VisibleForTesting 050 FilterExecutor(DatabaseSession session, Dialect dialect) { 051 this.session = session; 052 this.dialect = dialect; 053 } 054 055 public FilterResult execute(Filter filter) { 056 if (filter.mustReturnEmptyResult()) { 057 return new FilterResult(filter, Collections.<Object[]> emptyList()); 058 } 059 060 String sql = null; 061 try { 062 TimeProfiler profiler = new TimeProfiler(FilterExecutor.class).setLevelToDebug().start("Build/execute SQL query"); 063 sql = toSql(filter); 064 LOG.debug("SQL: " + sql); 065 Query query = session.getEntityManager().createNativeQuery(sql); 066 setHqlParameters(filter, query); 067 FilterResult result = new FilterResult(filter, query.getResultList()); 068 profiler.stop(); 069 070 profiler.start("Process rows"); 071 result.removeUnvalidRows(); 072 profiler.stop(); 073 074 profiler.start("Sort rows"); 075 result.sort(); 076 profiler.stop(); 077 return result; 078 079 } catch (Exception e) { 080 throw new SonarException("Fail to execute filter: " + filter.toString() + ", sql=" + sql, e); 081 } 082 } 083 084 @VisibleForTesting 085 String toSql(Filter filter) { 086 StringBuilder sql = new StringBuilder(SQL_INITIAL_SIZE); 087 addSelectColumns(filter, sql); 088 addFromClause(filter, sql); 089 addWhereClause(filter, sql); 090 return sql.toString(); 091 } 092 093 private void addSelectColumns(Filter filter, StringBuilder sql) { 094 sql.append("SELECT s.id, MAX(s.project_id) as pid, MAX(s.root_project_id) as rpid"); 095 if (filter.isSortedByLanguage()) { 096 sql.append(", MAX(p.language) as lang "); 097 098 } else if (filter.isSortedByName()) { 099 sql.append(", MAX(p.long_name) as name "); 100 101 } else if (filter.isSortedByKey()) { 102 sql.append(", MAX(p.kee) as kee "); 103 104 } else if (filter.isSortedByDate()) { 105 sql.append(", MAX(s.created_at) as createdat "); 106 107 } else if (filter.isSortedByVersion()) { 108 sql.append(", MAX(s.version) as version "); 109 } 110 if (filter.getSortedMetricId() != null) { 111 sql.append(", MAX(CASE WHEN pm.metric_id="); 112 sql.append(filter.getSortedMetricId()); 113 sql.append(" THEN "); 114 sql.append(filter.getColumnToSort()); 115 sql.append(" ELSE NULL END) AS sortvalue"); 116 sql.append(" "); 117 } 118 for (int index = 0; index < filter.getMeasureCriteria().size(); index++) { 119 MeasureCriterion criterion = filter.getMeasureCriteria().get(index); 120 String column = (criterion.isVariation() ? Filter.getVariationColumn(filter.getPeriodIndex()) : "value"); 121 sql.append(", MAX(CASE WHEN pm.metric_id="); 122 sql.append(criterion.getMetricId()); 123 sql.append(" AND pm."); 124 sql.append(column); 125 sql.append(criterion.getOperator()); 126 sql.append(criterion.getValue()); 127 sql.append(" THEN "); 128 sql.append(column); 129 sql.append(" ELSE NULL END) AS crit_"); 130 sql.append(index); 131 sql.append(" "); 132 } 133 } 134 135 private void addFromClause(Filter filter, StringBuilder sql) { 136 sql.append(" FROM snapshots s "); 137 if (filter.mustJoinMeasuresTable()) { 138 sql.append(" INNER JOIN project_measures pm "); 139 if (MsSql.ID.equals(dialect.getId())) { 140 // SONAR-3422 141 sql.append(" WITH (INDEX(measures_sid_metric)) "); 142 } 143 sql.append(" ON s.id=pm.snapshot_id "); 144 } 145 sql.append(" INNER JOIN projects p ON s.project_id=p.id "); 146 } 147 148 private void addWhereClause(Filter filter, StringBuilder sql) { 149 sql.append(" WHERE "); 150 if (filter.mustJoinMeasuresTable()) { 151 if (filter.hasMeasureCriteria()) { 152 sql.append(" ( "); 153 int index = 0; 154 while (index < filter.getMeasureCriteria().size()) { 155 if (index > 0) { 156 sql.append(" OR "); 157 } 158 MeasureCriterion criterion = filter.getMeasureCriteria().get(index); 159 String column = (criterion.isVariation() ? Filter.getVariationColumn(filter.getPeriodIndex()) : "value"); 160 sql.append("(pm.metric_id=").append(criterion.getMetricId()).append(" and pm.").append(column) 161 .append(criterion.getOperator()).append(criterion.getValue()).append(")"); 162 index++; 163 } 164 165 if (filter.getSortedMetricId() != null && !filter.hasMeasureCriteriaOnMetric(filter.getSortedMetricId())) { 166 sql.append(" OR (pm.metric_id=").append(filter.getSortedMetricId()).append(") "); 167 } 168 169 sql.append(" ) AND "); 170 } 171 sql.append(" pm.rule_id IS NULL AND pm.rule_priority IS NULL"); 172 sql.append(" AND pm.characteristic_id IS NULL"); 173 sql.append(" AND pm.person_id IS NULL"); 174 sql.append(" AND "); 175 } 176 sql.append(" s.status=:status AND s.islast=:islast "); 177 if (filter.getScopes() != null) { 178 sql.append(filter.getScopes().isEmpty() ? " AND s.scope IS NULL " : " AND s.scope IN (:scopes) "); 179 } 180 if (filter.hasQualifiers()) { 181 sql.append(" AND s.qualifier IN (:qualifiers) "); 182 } else if (!filter.isOnDirectChildren()) { 183 // no criteria -> we should not display all rows but no rows 184 sql.append(" AND s.qualifier IS NULL "); 185 } 186 if (filter.hasLanguages()) { 187 sql.append(" AND p.language IN (:languages) "); 188 } 189 if (filter.getFavouriteIds() != null) { 190 sql.append(filter.getFavouriteIds().isEmpty() ? " AND s.project_id IS NULL " : " AND s.project_id IN (:favourites) "); 191 } 192 if (filter.hasBaseSnapshot()) { 193 if (filter.isOnDirectChildren()) { 194 sql.append(" AND s.parent_snapshot_id=:parent_sid "); 195 } else { 196 sql.append(" AND s.root_snapshot_id=:root_sid AND s.path LIKE :path "); 197 } 198 } 199 if (filter.getDateCriterion() != null) { 200 sql.append(" AND s.created_at"); 201 sql.append(filter.getDateCriterion().getOperator()); 202 sql.append(" :date "); 203 } 204 if (StringUtils.isNotBlank(filter.getKeyRegexp())) { 205 sql.append(" AND UPPER(p.kee) LIKE :kee"); 206 } 207 if (StringUtils.isNotBlank(filter.getNameRegexp())) { 208 sql.append(" AND UPPER(p.long_name) LIKE :name"); 209 } 210 if (!filter.hasBaseSnapshot()) { 211 sql.append(" AND p.copy_resource_id IS NULL "); 212 } 213 sql.append(" GROUP BY s.id"); 214 } 215 216 private void setHqlParameters(Filter filter, Query query) { 217 query.setParameter("status", Snapshot.STATUS_PROCESSED); 218 query.setParameter("islast", true); 219 if (filter.hasScopes()) { 220 query.setParameter("scopes", filter.getScopes()); 221 } 222 if (filter.hasQualifiers()) { 223 query.setParameter("qualifiers", filter.getQualifiers()); 224 } 225 if (filter.hasLanguages()) { 226 query.setParameter("languages", filter.getLanguages()); 227 } 228 if (filter.hasFavouriteIds()) { 229 query.setParameter("favourites", filter.getFavouriteIds()); 230 } 231 if (filter.getDateCriterion() != null) { 232 query.setParameter("date", filter.getDateCriterion().getDate()); 233 } 234 if (filter.hasBaseSnapshot()) { 235 if (filter.isOnDirectChildren()) { 236 query.setParameter("parent_sid", filter.getBaseSnapshotId()); 237 } else { 238 query.setParameter("root_sid", filter.getRootSnapshotId()); 239 query.setParameter("path", new StringBuilder().append( 240 filter.getBaseSnapshotPath()).append(filter.getBaseSnapshotId()).append(".%").toString()); 241 } 242 } 243 if (StringUtils.isNotBlank(filter.getKeyRegexp())) { 244 query.setParameter("kee", StringUtils.upperCase(StringUtils.replaceChars(filter.getKeyRegexp(), '*', '%'))); 245 } 246 if (StringUtils.isNotBlank(filter.getNameRegexp())) { 247 query.setParameter("name", StringUtils.upperCase(StringUtils.replaceChars(filter.getNameRegexp(), '*', '%'))); 248 } 249 } 250}