<artifactId>sonar-testing-harness</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>com.github.tlrx</groupId>
+ <artifactId>elasticsearch-test</artifactId>
+ <version>${elasticsearch.version}</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
*/
package org.sonar.server.platform;
+import org.sonar.server.rule.RuleRegistry;
+
import org.apache.commons.configuration.BaseConfiguration;
import org.slf4j.LoggerFactory;
import org.sonar.api.config.EmailSettings;
startupContainer.addSingleton(LogServerId.class);
startupContainer.addSingleton(RegisterServletFilters.class);
startupContainer.addSingleton(CleanDryRunCache.class);
- startupContainer.addSingleton(IndexRules.class);
+ startupContainer.addSingleton(RuleRegistry.class);
startupContainer.startComponents();
startupContainer.getComponentByType(ServerLifecycleNotifier.class).notifyStart();
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.rule;
+
+import org.elasticsearch.common.collect.Lists;
+import org.elasticsearch.common.io.BytesStream;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleParam;
+import org.sonar.core.i18n.RuleI18nManager;
+import org.sonar.jpa.session.DatabaseSessionFactory;
+import org.sonar.server.search.SearchIndex;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Fill search index with rules
+ * @since 4.1
+ */
+public final class RuleRegistry {
+
+ private static final String INDEX_RULES = "rules";
+ private static final String TYPE_RULE = "rule";
+
+ private SearchIndex searchIndex;
+ private DatabaseSessionFactory sessionFactory;
+ private RuleI18nManager ruleI18nManager;
+
+ public RuleRegistry(SearchIndex searchIndex, DatabaseSessionFactory sessionFactory, RuleI18nManager ruleI18nManager) {
+ this.searchIndex = searchIndex;
+ this.sessionFactory = sessionFactory;
+ this.ruleI18nManager = ruleI18nManager;
+ }
+
+ public void start() {
+ searchIndex.addMappingFromClasspath(INDEX_RULES, TYPE_RULE, "/com/sonar/search/rule_mapping.json");
+ }
+
+ public void bulkRegisterRules() {
+ DatabaseSession session = sessionFactory.getSession();
+
+ try {
+ List<String> ids = Lists.newArrayList();
+ List<BytesStream> docs = Lists.newArrayList();
+ for (Rule rule: session.getResults(Rule.class)) {
+ ids.add(rule.getId().toString());
+ XContentBuilder document = XContentFactory.jsonBuilder()
+ .startObject()
+ .field("id", rule.getId())
+ .field("key", rule.ruleKey())
+ .field("language", rule.getLanguage())
+ .field("name", ruleI18nManager.getName(rule, Locale.getDefault()))
+ .field("description", ruleI18nManager.getDescription(rule.getRepositoryKey(), rule.getKey(), Locale.getDefault()))
+ .field("parentKey", rule.getParent() == null ? null : rule.getParent().getKey())
+ .field("repositoryKey", rule.getRepositoryKey())
+ .field("severity", rule.getSeverity())
+ .field("status", rule.getStatus())
+ .field("createdAt", rule.getCreatedAt())
+ .field("updatedAt", rule.getUpdatedAt());
+ if(!rule.getParams().isEmpty()) {
+ document.startArray("params");
+ for (RuleParam param: rule.getParams()) {
+ document.startObject()
+ .field("key", param.getKey())
+ .field("type", param.getType())
+ .field("defaultValue", param.getDefaultValue())
+ .field("description", param.getDescription())
+ .endObject();
+ }
+ document.endArray();
+ }
+ docs.add(document.endObject());
+ }
+ searchIndex.bulkIndex(INDEX_RULES, TYPE_RULE, ids.toArray(new String[0]), docs.toArray(new BytesStream[0]));
+ } catch(IOException ioe) {
+ throw new IllegalStateException("Unable to index rules", ioe);
+ }
+ }
+}
package org.sonar.server.search;
import org.apache.commons.io.IOUtils;
+import org.elasticsearch.ElasticSearchParseException;
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.client.IndicesAdminClient;
import org.elasticsearch.client.Requests;
import org.elasticsearch.common.io.BytesStream;
-import org.elasticsearch.index.query.QueryBuilders;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
client.prepareIndex(index, type, id).setSource(source.bytes()).execute().actionGet();
}
- public void put(String index, String type, String id, BytesStream source, String parent) {
- client.prepareIndex(index, type, id).setParent(parent).setSource(source.bytes()).execute().actionGet();
- }
-
public void bulkIndex(String index, String type, String[] ids, BytesStream[] sources) {
BulkRequestBuilder builder = new BulkRequestBuilder(client);
for (int i=0; i<ids.length; i++) {
public void addMappingFromClasspath(String index, String type, String resourcePath) {
try {
addMapping(index, type, IOUtils.toString(getClass().getResource(resourcePath)));
+ } catch(NullPointerException nonExisting) {
+ throw new IllegalArgumentException("Could not load unexisting file at " + resourcePath, nonExisting);
} catch(IOException ioException) {
- throw new IllegalStateException("Could not find mapping in classpath at "+resourcePath, ioException);
+ throw new IllegalArgumentException("Problem loading file at " + resourcePath, ioException);
}
}
} catch (Exception e) {
LOG.error("While checking for index existence", e);
}
- indices.putMapping(Requests.putMappingRequest(index).type(type).source(mapping)).actionGet();
- }
- public void stats(String index) {
- LOG.info(
- String.format(
- "Index %s contains %d elements", index,
- client.prepareSearch(index).setQuery(QueryBuilders.matchAllQuery()).execute().actionGet().getHits().totalHits()));
+ try {
+ indices.putMapping(Requests.putMappingRequest(index).type(type).source(mapping)).actionGet();
+ } catch(ElasticSearchParseException parseException) {
+ throw new IllegalArgumentException("Invalid mapping file", parseException);
+ }
}
public SearchResponse find(SearchQuery query) {
public void start() {
LOG.info("Starting {} in {}", this.getClass().getSimpleName(), nodeDir);
nodeSettingsBuilder
+ .put("node.name", "sonarqube")
.put("node.path.conf", nodeDir)
.put("node.path.data", nodeDir)
.put("node.path.work", nodeDir)
+++ /dev/null
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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 this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-package org.sonar.server.startup;
-
-import org.elasticsearch.common.collect.Lists;
-import org.elasticsearch.common.io.BytesStream;
-import org.elasticsearch.common.xcontent.XContentBuilder;
-import org.elasticsearch.common.xcontent.XContentFactory;
-import org.sonar.api.database.DatabaseSession;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleParam;
-import org.sonar.core.i18n.RuleI18nManager;
-import org.sonar.server.search.SearchIndex;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Fill search index with rules
- * @since 4.1
- */
-public final class IndexRules {
-
- private static final String INDEX_RULES = "rules";
- private static final String TYPE_RULE = "rule";
-
- private SearchIndex searchIndex;
- private DatabaseSession session;
- private RuleI18nManager ruleI18nManager;
-
- public IndexRules(SearchIndex searchIndex, DatabaseSession session, RuleI18nManager ruleI18nManager) {
- this.searchIndex = searchIndex;
- this.session = session;
- this.ruleI18nManager = ruleI18nManager;
- }
-
- public void start() {
-
- try {
- searchIndex.addMappingFromClasspath(INDEX_RULES, TYPE_RULE, "/com/sonar/search/rule_mapping.json");
- List<String> ids = Lists.newArrayList();
- List<BytesStream> docs = Lists.newArrayList();
- for (Rule rule: session.getResults(Rule.class)) {
- ids.add(rule.getId().toString());
- XContentBuilder document = XContentFactory.jsonBuilder()
- .startObject()
- .field("id", rule.getId())
- .field("key", rule.ruleKey())
- .field("language", rule.getLanguage())
- .field("name", ruleI18nManager.getName(rule, Locale.getDefault()))
- .field("description", ruleI18nManager.getDescription(rule.getRepositoryKey(), rule.getKey(), Locale.getDefault()))
- .field("parentKey", rule.getParent() == null ? null : rule.getParent().getKey())
- .field("repositoryKey", rule.getRepositoryKey())
- .field("severity", rule.getSeverity())
- .field("status", rule.getStatus())
- .field("createdAt", rule.getCreatedAt())
- .field("updatedAt", rule.getUpdatedAt());
- if(!rule.getParams().isEmpty()) {
- document.startArray("params");
- for (RuleParam param: rule.getParams()) {
- document.startObject()
- .field("key", param.getKey())
- .field("type", param.getType())
- .field("defaultValue", param.getDefaultValue())
- .field("description", param.getDescription())
- .endObject();
- }
- document.endArray();
- }
- docs.add(document.endObject());
- }
- searchIndex.bulkIndex(INDEX_RULES, TYPE_RULE, ids.toArray(new String[0]), docs.toArray(new BytesStream[0]));
- } catch(IOException ioe) {
- throw new IllegalStateException("Unable to index rules", ioe);
- }
- }
-}
package org.sonar.server.search;
-import org.elasticsearch.client.Client;
+import com.github.tlrx.elasticsearch.test.EsSetup;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class SearchIndexTest {
+ private EsSetup esSetup;
private SearchNode searchNode;
- private Client client;
-
private SearchIndex searchIndex;
@Before
public void setUp() {
+ esSetup = new EsSetup();
+ esSetup.execute(EsSetup.deleteAll());
+
searchNode = mock(SearchNode.class);
- client = mock(Client.class);
- when(searchNode.client()).thenReturn(client);
+ when(searchNode.client()).thenReturn(esSetup.client());
searchIndex = new SearchIndex(searchNode);
+ searchIndex.start();
+ }
+
+ @After
+ public void tearDown() {
+ esSetup.terminate();
}
@Test
public void should_start_and_stop_properly() {
+ verify(searchNode).client();
+ searchIndex.stop();
+ }
+
+ @Test
+ public void should_create_index_when_loading_mapping_from_classpath() {
+ String index = "index";
+ String type = "type";
+ String resourcePath = "/org/sonar/server/search/SearchIndexTest/correct_mapping1.json";
+
searchIndex.start();
+ searchIndex.addMappingFromClasspath(index, type, resourcePath);
- verify(searchNode).client();
+ assertThat(esSetup.exists(index)).isTrue();
+ }
- searchIndex.stop();
+ @Test
+ public void should_reuse_index_when_loading_mapping_from_classpath() {
+ String index = "index";
+ String type1 = "type1";
+ String type2 = "type2";
+ String resourcePath1 = "/org/sonar/server/search/SearchIndexTest/correct_mapping1.json";
+ String resourcePath2 = "/org/sonar/server/search/SearchIndexTest/correct_mapping2.json";
+
+ searchIndex.start();
+ searchIndex.addMappingFromClasspath(index, type1, resourcePath1);
+ searchIndex.addMappingFromClasspath(index, type2, resourcePath2);
- verify(client).close();
+ assertThat(esSetup.exists(index)).isTrue();
}
+
+
+ @Test(expected = IllegalArgumentException.class)
+ public void should_fail_to_load_inexistent_mapping() {
+ String resourcePath = "/org/sonar/server/search/SearchIndexTest/inexistent.json";
+
+ searchIndex.start();
+ searchIndex.addMappingFromClasspath("unchecked", "unchecked", resourcePath);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void should_fail_to_load_malformed_mapping() {
+ String resourcePath = "/org/sonar/server/search/SearchIndexTest/malformed.json";
+
+ searchIndex.start();
+ searchIndex.addMappingFromClasspath("unchecked", "unchecked", resourcePath);
+ }
+
}
--- /dev/null
+{
+ "type1": {
+ "properties": {
+ "value": {
+ "type": "string"
+ }
+ }
+ }
+}
--- /dev/null
+{
+ "type2": {
+ "properties": {
+ "value": {
+ "type": "string"
+ }
+ }
+ }
+}