]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5410 - created sonar-search module for SQ
authorStephane Gamard <stephane.gamard@searchbox.com>
Wed, 9 Jul 2014 14:10:08 +0000 (16:10 +0200)
committerStephane Gamard <stephane.gamard@searchbox.com>
Wed, 9 Jul 2014 14:32:26 +0000 (16:32 +0200)
pom.xml
sonar-search/pom.xml [new file with mode: 0644]
sonar-search/src/main/java/org/sonar/search/script/ListUpdate.java [new file with mode: 0644]
sonar-search/src/main/resources/logback.xml [new file with mode: 0644]
sonar-search/src/test/java/org/sonar/search/ElasticSearchTest.java [new file with mode: 0644]

diff --git a/pom.xml b/pom.xml
index 0104ee7a8139c53c2b0cc0d94507ab2c5e1aa73d..598d953b7b4095d2643e39012e22c7dc6aa02e6e 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -13,6 +13,7 @@
   <modules>
     <module>sonar-application</module>
     <module>sonar-process</module>
+    <module>sonar-search</module>
     <module>sonar-batch</module>
     <module>sonar-batch-maven-compat</module>
     <module>sonar-check-api</module>
diff --git a/sonar-search/pom.xml b/sonar-search/pom.xml
new file mode 100644 (file)
index 0000000..b7b6e01
--- /dev/null
@@ -0,0 +1,58 @@
+<?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">
+  <parent>
+    <groupId>org.codehaus.sonar</groupId>
+    <artifactId>sonar</artifactId>
+    <version>4.5-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+
+  <artifactId>sonar-search</artifactId>
+  <packaging>jar</packaging>
+  <name>SonarQube :: Search</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.codehaus.sonar</groupId>
+      <artifactId>sonar-process</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.elasticsearch</groupId>
+      <artifactId>elasticsearch</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>ch.qos.logback</groupId>
+      <artifactId>logback-classic</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.easytesting</groupId>
+      <artifactId>fest-assert</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.hamcrest</groupId>
+      <artifactId>hamcrest-all</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
\ No newline at end of file
diff --git a/sonar-search/src/main/java/org/sonar/search/script/ListUpdate.java b/sonar-search/src/main/java/org/sonar/search/script/ListUpdate.java
new file mode 100644 (file)
index 0000000..79a8a03
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.search.script;
+
+import com.google.common.collect.ImmutableSet;
+import org.elasticsearch.common.Nullable;
+import org.elasticsearch.common.xcontent.support.XContentMapValues;
+import org.elasticsearch.script.AbstractExecutableScript;
+import org.elasticsearch.script.ExecutableScript;
+import org.elasticsearch.script.NativeScriptFactory;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * @since 4.4
+ */
+public class ListUpdate extends AbstractExecutableScript {
+
+  public static final String NAME = "listUpdate";
+
+  public static final String ID_FIELD = "idField";
+  public static final String ID_VALUE = "idValue";
+  public static final String FIELD = "field";
+  public static final String VALUE = "value";
+
+  public static class UpdateListScriptFactory implements NativeScriptFactory {
+    @Override
+    public ExecutableScript newScript(@Nullable Map<String, Object> params) {
+      String idField = XContentMapValues.nodeStringValue(params.get(ID_FIELD), null);
+      String idValue = XContentMapValues.nodeStringValue(params.get(ID_VALUE), null);
+      String field = XContentMapValues.nodeStringValue(params.get(FIELD), null);
+      Map value = null;
+      if (idField == null) {
+        throw new IllegalStateException("Missing '" + ID_FIELD + "' parameter");
+      }
+      if (idValue == null) {
+        throw new IllegalStateException("Missing '" + ID_VALUE + "' parameter");
+      }
+      if (field == null) {
+        throw new IllegalStateException("Missing '" + FIELD + "' parameter");
+      }
+
+      //NULL case is deletion of nested item
+      if (params.containsKey(VALUE)) {
+        Object obj = params.get(VALUE);
+        if (obj != null) {
+          value = XContentMapValues.nodeMapValue(params.get(VALUE), "Update item");
+        }
+      }
+
+      return new ListUpdate(idField, idValue, field, value);
+    }
+  }
+
+
+  private final String idField;
+  private final String idValue;
+  private final String field;
+  private final Map<String, Object> value;
+
+  private Map<String, Object> ctx;
+
+  public ListUpdate(String idField, String idValue, String field, Map<String, Object> value) {
+    this.idField = idField;
+    this.idValue = idValue;
+    this.field = field;
+    this.value = value;
+  }
+
+  @Override
+  public void setNextVar(String name, Object value) {
+    if (name.equals("ctx")) {
+      ctx = (Map<String, Object>) value;
+    }
+  }
+
+  @Override
+  public Object unwrap(Object value) {
+    return value;
+  }
+
+  @Override
+  public Object run() {
+    try {
+      //Get the Document's source from ctx
+      Map<String, Object> source = XContentMapValues.nodeMapValue(ctx.get("_source"), "source from context");
+
+      //Get the Object for list update
+      Object fieldValue = source.get(field);
+
+      if (fieldValue == null && value != null) {
+        // 0. The field does not exist (this is a upsert then)
+        source.put(field, value);
+      } else if (!XContentMapValues.isArray(fieldValue) && value != null) {
+        // 1. The field is not yet a list
+        Map currentFieldValue = XContentMapValues.nodeMapValue(fieldValue, "current FieldValue");
+        if (XContentMapValues.nodeStringValue(currentFieldValue.get(idField), null).equals(idValue)) {
+          source.put(field, value);
+        } else {
+          source.put(field, ImmutableSet.of(fieldValue, value));
+        }
+      } else {
+        // 3. field is a list
+        Collection items = (Collection) fieldValue;
+        Object target = null;
+        for (Object item : items) {
+          Map<String, Object> fields = (Map<String, Object>) item;
+          String itemIdValue = XContentMapValues.nodeStringValue(fields.get(idField), null);
+          if (itemIdValue != null && itemIdValue.equals(idValue)) {
+            target = item;
+            break;
+          }
+        }
+        if (target != null) {
+          items.remove(target);
+        }
+
+        //Supporting the update by NULL = deletion case
+        if (value != null) {
+          items.add(value);
+        }
+        source.put(field, items);
+      }
+    } catch (Exception e) {
+      throw new IllegalStateException("failed to execute listUpdate script", e);
+    }
+    return null;
+
+  }
+}
diff --git a/sonar-search/src/main/resources/logback.xml b/sonar-search/src/main/resources/logback.xml
new file mode 100644 (file)
index 0000000..d873268
--- /dev/null
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!--
+  Configuration for default logger. Only used while embedded server is starting,
+  before proper logging configuration is loaded.
+
+  See http://logback.qos.ch/manual/configuration.html
+-->
+<configuration debug="false">
+  <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/>
+
+  <appender name="LOGFILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+    <File>${SONAR_HOME}/logs/sonar.log</File>
+    <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
+      <param name="FileNamePattern" value="${SONAR_HOME}/logs/sonar.%i.log"/>
+      <param name="MinIndex" value="1"/>
+      <param name="MaxIndex" value="3"/>
+    </rollingPolicy>
+    <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
+      <param name="MaxFileSize" value="5MB"/>
+    </triggeringPolicy>
+    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+      <!-- Use %d{yyyy.MM.dd HH:mm:ss.SSS} to display milliseconds -->
+      <pattern>
+        %d{yyyy.MM.dd HH:mm:ss} %-5level [%logger{20}] %X %msg%n
+      </pattern>
+    </encoder>
+  </appender>
+
+  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+      <pattern>
+        %d{yyyy.MM.dd HH:mm:ss} %-5level %msg%n
+      </pattern>
+    </encoder>
+  </appender>
+
+  <logger name="console" additivity="false">
+    <level value="INFO"/>
+    <appender-ref ref="CONSOLE"/>
+  </logger>
+
+  <root>
+    <level value="INFO"/>
+    <appender-ref ref="CONSOLE"/>
+  </root>
+
+</configuration>
diff --git a/sonar-search/src/test/java/org/sonar/search/ElasticSearchTest.java b/sonar-search/src/test/java/org/sonar/search/ElasticSearchTest.java
new file mode 100644 (file)
index 0000000..99d8379
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 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.search;
+
+import org.elasticsearch.action.admin.cluster.health.ClusterHealthStatus;
+import org.elasticsearch.client.transport.TransportClient;
+import org.elasticsearch.common.settings.ImmutableSettings;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.transport.InetSocketTransportAddress;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.SocketException;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class ElasticSearchTest {
+
+  DatagramSocket socket;
+  ElasticSearch elasticSearch;
+
+  @Before
+  public void setup() throws SocketException {
+    socket = new DatagramSocket(0);
+    elasticSearch = new ElasticSearch("test", socket.getLocalPort());
+  }
+
+  @After
+  public void tearDown() {
+    socket.close();
+    elasticSearch.shutdown();
+  }
+
+  @Test
+  public void can_connect() throws IOException, InterruptedException {
+
+    // 0 assert that application is running
+    assertHeartBeat();
+
+    Settings settings = ImmutableSettings.settingsBuilder()
+      .put("client.transport.sniff", true)
+      .put("cluster.name", "sonarqube")
+      .build();
+    TransportClient client = new TransportClient(settings)
+      .addTransportAddress(new InetSocketTransportAddress("localhost", 9300));
+
+
+    // 0 assert that we have a OK cluster available
+    assertThat(client.admin().cluster().prepareClusterStats().get().getStatus()).isEqualTo(ClusterHealthStatus.GREEN);
+
+    // 1 assert that we get a heartBeat from the application
+    assertHeartBeat();
+  }
+
+  private void assertHeartBeat() throws IOException {
+    DatagramPacket packet = new DatagramPacket(new byte[1024], 1024);
+    socket.receive(packet);
+    assertThat(packet.getData()).isNotNull();
+  }
+}
\ No newline at end of file