From 64bbefd3ac6f28def42ab16daa2b3bde884a9263 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 13 Jun 2016 14:38:34 +0200 Subject: [PATCH] Add Jest-based health indicator This commit adds a Jest-based health indicator for ElasticSearch. If both Jest and the Spring Data are available, the latter takes precedence as it provides more information. Closes gh-3178 --- spring-boot-actuator/pom.xml | 5 + ...CompositeHealthIndicatorConfiguration.java | 69 +++++++++++++ ...ticsearchHealthIndicatorConfiguration.java | 96 +++++++++++++++++++ .../HealthIndicatorAutoConfiguration.java | 92 ++---------------- .../ElasticsearchJestHealthIndicator.java | 55 +++++++++++ ...HealthIndicatorAutoConfigurationTests.java | 33 ++++++- ...ElasticsearchJestHealthIndicatorTests.java | 86 +++++++++++++++++ 7 files changed, 351 insertions(+), 85 deletions(-) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/CompositeHealthIndicatorConfiguration.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ElasticsearchHealthIndicatorConfiguration.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ElasticsearchJestHealthIndicator.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ElasticsearchJestHealthIndicatorTests.java diff --git a/spring-boot-actuator/pom.xml b/spring-boot-actuator/pom.xml index cf323298cb..1b23af54ca 100644 --- a/spring-boot-actuator/pom.xml +++ b/spring-boot-actuator/pom.xml @@ -46,6 +46,11 @@ jackson-dataformat-xml true + + io.searchbox + jest + true + org.springframework.hateoas spring-hateoas diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/CompositeHealthIndicatorConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/CompositeHealthIndicatorConfiguration.java new file mode 100644 index 0000000000..33dc0885e6 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/CompositeHealthIndicatorConfiguration.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure; + +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.health.CompositeHealthIndicator; +import org.springframework.boot.actuate.health.HealthAggregator; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.core.ResolvableType; + +/** + * Base class for configurations that can combine source beans using a + * {@link CompositeHealthIndicator}. + * + * @param The health indicator type + * @param T he bean source type + * @since 1.4.0 + */ +public abstract class CompositeHealthIndicatorConfiguration { + + @Autowired + private HealthAggregator healthAggregator; + + protected HealthIndicator createHealthIndicator(Map beans) { + if (beans.size() == 1) { + return createHealthIndicator(beans.values().iterator().next()); + } + CompositeHealthIndicator composite = new CompositeHealthIndicator( + this.healthAggregator); + for (Map.Entry entry : beans.entrySet()) { + composite.addHealthIndicator(entry.getKey(), + createHealthIndicator(entry.getValue())); + } + return composite; + } + + @SuppressWarnings("unchecked") + protected H createHealthIndicator(S source) { + Class[] generics = ResolvableType + .forClass(CompositeHealthIndicatorConfiguration.class, getClass()) + .resolveGenerics(); + Class indicatorClass = (Class) generics[0]; + Class sourceClass = (Class) generics[1]; + try { + return indicatorClass.getConstructor(sourceClass).newInstance(source); + } + catch (Exception ex) { + throw new IllegalStateException("Unable to create indicator " + + indicatorClass + " for source " + sourceClass, ex); + } + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ElasticsearchHealthIndicatorConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ElasticsearchHealthIndicatorConfiguration.java new file mode 100644 index 0000000000..fd5e81915b --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ElasticsearchHealthIndicatorConfiguration.java @@ -0,0 +1,96 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure; + +import java.util.Map; + +import io.searchbox.client.JestClient; +import org.elasticsearch.client.Client; + +import org.springframework.boot.actuate.health.ElasticsearchHealthIndicator; +import org.springframework.boot.actuate.health.ElasticsearchHealthIndicatorProperties; +import org.springframework.boot.actuate.health.ElasticsearchJestHealthIndicator; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Actual ElasticSearch health indicator configurations imported by + * {@link HealthIndicatorAutoConfiguration}. + * + * @author Stephane Nicoll + */ +class ElasticsearchHealthIndicatorConfiguration { + + @Configuration + @ConditionalOnBean(Client.class) + @ConditionalOnEnabledHealthIndicator("elasticsearch") + @EnableConfigurationProperties(ElasticsearchHealthIndicatorProperties.class) + static class SpringData extends + CompositeHealthIndicatorConfiguration { + + private final Map clients; + + private final ElasticsearchHealthIndicatorProperties properties; + + SpringData(Map clients, + ElasticsearchHealthIndicatorProperties properties) { + this.clients = clients; + this.properties = properties; + } + + @Bean + @ConditionalOnMissingBean(name = "elasticsearchHealthIndicator") + public HealthIndicator elasticsearchHealthIndicator() { + return createHealthIndicator(this.clients); + } + + @Override + protected ElasticsearchHealthIndicator createHealthIndicator(Client client) { + return new ElasticsearchHealthIndicator(client, this.properties); + } + + } + + @Configuration + @ConditionalOnBean(JestClient.class) + @ConditionalOnEnabledHealthIndicator("elasticsearch") + static class Jest extends + CompositeHealthIndicatorConfiguration { + + private final Map clients; + + Jest(Map clients) { + this.clients = clients; + } + + @Bean + @ConditionalOnMissingBean(name = "elasticsearchHealthIndicator") + public HealthIndicator elasticsearchHealthIndicator() { + return createHealthIndicator(this.clients); + } + + @Override + protected ElasticsearchJestHealthIndicator createHealthIndicator(JestClient client) { + return new ElasticsearchJestHealthIndicator(client); + } + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java index c6ee089620..5b9dd66540 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java @@ -25,21 +25,16 @@ import javax.sql.DataSource; import com.couchbase.client.java.Bucket; import com.datastax.driver.core.Cluster; import org.apache.solr.client.solrj.SolrClient; -import org.elasticsearch.client.Client; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.health.CassandraHealthIndicator; -import org.springframework.boot.actuate.health.CompositeHealthIndicator; import org.springframework.boot.actuate.health.CouchbaseHealthIndicator; import org.springframework.boot.actuate.health.DataSourceHealthIndicator; import org.springframework.boot.actuate.health.DiskSpaceHealthIndicator; import org.springframework.boot.actuate.health.DiskSpaceHealthIndicatorProperties; -import org.springframework.boot.actuate.health.ElasticsearchHealthIndicator; -import org.springframework.boot.actuate.health.ElasticsearchHealthIndicatorProperties; import org.springframework.boot.actuate.health.HealthAggregator; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.JmsHealthIndicator; @@ -66,6 +61,7 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadata; import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvider; import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProviders; +import org.springframework.boot.autoconfigure.jest.JestAutoConfiguration; import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration; import org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration; import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; @@ -73,7 +69,7 @@ import org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.ResolvableType; +import org.springframework.context.annotation.Import; import org.springframework.data.cassandra.core.CassandraOperations; import org.springframework.data.couchbase.core.CouchbaseOperations; import org.springframework.data.mongodb.core.MongoTemplate; @@ -96,12 +92,14 @@ import org.springframework.mail.javamail.JavaMailSenderImpl; @AutoConfigureBefore({ EndpointAutoConfiguration.class }) @AutoConfigureAfter({ CassandraAutoConfiguration.class, CassandraDataAutoConfiguration.class, CouchbaseAutoConfiguration.class, - DataSourceAutoConfiguration.class, MongoAutoConfiguration.class, - MongoDataAutoConfiguration.class, RedisAutoConfiguration.class, - RabbitAutoConfiguration.class, SolrAutoConfiguration.class, - MailSenderAutoConfiguration.class, JmsAutoConfiguration.class, - ElasticsearchAutoConfiguration.class }) + DataSourceAutoConfiguration.class, ElasticsearchAutoConfiguration.class, + JestAutoConfiguration.class, JmsAutoConfiguration.class, + MailSenderAutoConfiguration.class, MongoAutoConfiguration.class, + MongoDataAutoConfiguration.class, RabbitAutoConfiguration.class, + RedisAutoConfiguration.class, SolrAutoConfiguration.class }) @EnableConfigurationProperties({ HealthIndicatorProperties.class }) +@Import({ ElasticsearchHealthIndicatorConfiguration.SpringData.class, + ElasticsearchHealthIndicatorConfiguration.Jest.class }) public class HealthIndicatorAutoConfiguration { private final HealthIndicatorProperties properties; @@ -126,48 +124,6 @@ public class HealthIndicatorAutoConfiguration { return new ApplicationHealthIndicator(); } - /** - * Base class for configurations that can combine source beans using a - * {@link CompositeHealthIndicator}. - * @param The health indicator type - * @param The bean source type - */ - protected static abstract class CompositeHealthIndicatorConfiguration { - - @Autowired - private HealthAggregator healthAggregator; - - protected HealthIndicator createHealthIndicator(Map beans) { - if (beans.size() == 1) { - return createHealthIndicator(beans.values().iterator().next()); - } - CompositeHealthIndicator composite = new CompositeHealthIndicator( - this.healthAggregator); - for (Map.Entry entry : beans.entrySet()) { - composite.addHealthIndicator(entry.getKey(), - createHealthIndicator(entry.getValue())); - } - return composite; - } - - @SuppressWarnings("unchecked") - protected H createHealthIndicator(S source) { - Class[] generics = ResolvableType - .forClass(CompositeHealthIndicatorConfiguration.class, getClass()) - .resolveGenerics(); - Class indicatorClass = (Class) generics[0]; - Class sourceClass = (Class) generics[1]; - try { - return indicatorClass.getConstructor(sourceClass).newInstance(source); - } - catch (Exception ex) { - throw new IllegalStateException("Unable to create indicator " - + indicatorClass + " for source " + sourceClass, ex); - } - } - - } - @Configuration @ConditionalOnClass({ CassandraOperations.class, Cluster.class }) @ConditionalOnBean(CassandraOperations.class) @@ -401,34 +357,4 @@ public class HealthIndicatorAutoConfiguration { } - @Configuration - @ConditionalOnBean(Client.class) - @ConditionalOnEnabledHealthIndicator("elasticsearch") - @EnableConfigurationProperties(ElasticsearchHealthIndicatorProperties.class) - public static class ElasticsearchHealthIndicatorConfiguration extends - CompositeHealthIndicatorConfiguration { - - private final Map clients; - - private final ElasticsearchHealthIndicatorProperties properties; - - public ElasticsearchHealthIndicatorConfiguration(Map clients, - ElasticsearchHealthIndicatorProperties properties) { - this.clients = clients; - this.properties = properties; - } - - @Bean - @ConditionalOnMissingBean(name = "elasticsearchHealthIndicator") - public HealthIndicator elasticsearchHealthIndicator() { - return createHealthIndicator(this.clients); - } - - @Override - protected ElasticsearchHealthIndicator createHealthIndicator(Client client) { - return new ElasticsearchHealthIndicator(client, this.properties); - } - - } - } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ElasticsearchJestHealthIndicator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ElasticsearchJestHealthIndicator.java new file mode 100644 index 0000000000..792a7d81eb --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ElasticsearchJestHealthIndicator.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import io.searchbox.client.JestClient; +import io.searchbox.client.JestResult; +import io.searchbox.indices.Stats; + +/** + * {@link HealthIndicator} for Elasticsearch using a {@link JestClient}. + * + * @author Stephane Nicoll + * @since 1.4.0 + */ +public class ElasticsearchJestHealthIndicator extends AbstractHealthIndicator { + + private final JestClient jestClient; + + public ElasticsearchJestHealthIndicator(JestClient jestClient) { + this.jestClient = jestClient; + } + + @Override + protected void doHealthCheck(Health.Builder builder) throws Exception { + JestResult aliases = this.jestClient.execute(new Stats.Builder().build()); + JsonParser jsonParser = new JsonParser(); + JsonElement root = jsonParser.parse(aliases.getJsonString()); + JsonObject shards = root.getAsJsonObject().get("_shards").getAsJsonObject(); + int failedShards = shards.get("failed").getAsInt(); + if (failedShards != 0) { + builder.outOfService(); + } + else { + builder.up(); + } + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfigurationTests.java index 7d9f2d5579..090564f60f 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfigurationTests.java @@ -20,6 +20,7 @@ import java.util.Map; import javax.sql.DataSource; +import io.searchbox.client.JestClient; import org.junit.After; import org.junit.Test; @@ -29,6 +30,7 @@ import org.springframework.boot.actuate.health.CouchbaseHealthIndicator; import org.springframework.boot.actuate.health.DataSourceHealthIndicator; import org.springframework.boot.actuate.health.DiskSpaceHealthIndicator; import org.springframework.boot.actuate.health.ElasticsearchHealthIndicator; +import org.springframework.boot.actuate.health.ElasticsearchJestHealthIndicator; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.JmsHealthIndicator; @@ -46,6 +48,7 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvidersConfiguration; +import org.springframework.boot.autoconfigure.jest.JestAutoConfiguration; import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration; import org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration; import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; @@ -395,7 +398,8 @@ public class HealthIndicatorAutoConfigurationTests { EnvironmentTestUtils.addEnvironment(this.context, "spring.data.elasticsearch.properties.path.home:target", "management.health.diskspace.enabled:false"); - this.context.register(ElasticsearchAutoConfiguration.class, + this.context.register(JestClientConfiguration.class, + JestAutoConfiguration.class, ElasticsearchAutoConfiguration.class, ManagementServerProperties.class, HealthIndicatorAutoConfiguration.class); this.context.refresh(); @@ -406,13 +410,30 @@ public class HealthIndicatorAutoConfigurationTests { .isEqualTo(ElasticsearchHealthIndicator.class); } + @Test + public void elasticSearchJestHealthIndicator() { + EnvironmentTestUtils.addEnvironment(this.context, + "management.health.diskspace.enabled:false"); + this.context.register(JestClientConfiguration.class, + JestAutoConfiguration.class, ManagementServerProperties.class, + HealthIndicatorAutoConfiguration.class); + this.context.refresh(); + + Map beans = this.context + .getBeansOfType(HealthIndicator.class); + assertThat(beans).hasSize(1); + assertThat(beans.values().iterator().next().getClass()) + .isEqualTo(ElasticsearchJestHealthIndicator.class); + } + @Test public void notElasticSearchHealthIndicator() { EnvironmentTestUtils.addEnvironment(this.context, "management.health.elasticsearch.enabled:false", "spring.data.elasticsearch.properties.path.home:target", "management.health.diskspace.enabled:false"); - this.context.register(ElasticsearchAutoConfiguration.class, + this.context.register(JestClientConfiguration.class, + JestAutoConfiguration.class, ElasticsearchAutoConfiguration.class, ManagementServerProperties.class, HealthIndicatorAutoConfiguration.class); this.context.refresh(); @@ -502,4 +523,12 @@ public class HealthIndicatorAutoConfigurationTests { } + protected static class JestClientConfiguration { + + @Bean + public JestClient jestClient() { + return mock(JestClient.class); + } + } + } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ElasticsearchJestHealthIndicatorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ElasticsearchJestHealthIndicatorTests.java new file mode 100644 index 0000000000..2724b8d653 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ElasticsearchJestHealthIndicatorTests.java @@ -0,0 +1,86 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.io.IOException; + +import com.google.gson.Gson; +import com.google.gson.JsonParser; +import io.searchbox.action.Action; +import io.searchbox.client.JestClient; +import io.searchbox.client.JestResult; +import io.searchbox.client.config.exception.CouldNotConnectException; +import io.searchbox.core.SearchResult; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link ElasticsearchJestHealthIndicator}. + * + * @author Stephane Nicoll + */ +public class ElasticsearchJestHealthIndicatorTests { + + private final JestClient jestClient = mock(JestClient.class); + + private final ElasticsearchJestHealthIndicator healthIndicator = + new ElasticsearchJestHealthIndicator(this.jestClient); + + @Test + public void elasticSearchIsUp() throws IOException { + given(this.jestClient.execute(any(Action.class))) + .willReturn(createJestResult(4, 0)); + + Health health = this.healthIndicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + } + @Test + public void elasticSearchIsDown() throws IOException { + given(this.jestClient.execute(any(Action.class))).willThrow( + new CouldNotConnectException("http://localhost:9200", new IOException())); + + Health health = this.healthIndicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.DOWN); + } + + @Test + public void elasticSearchIsOutOfService() throws IOException { + given(this.jestClient.execute(any(Action.class))) + .willReturn(createJestResult(4, 1)); + + Health health = this.healthIndicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.OUT_OF_SERVICE); + } + + private static JestResult createJestResult(int shards, int failedShards) { + String json = String.format("{_shards: {\n" + + "total: %s,\n" + + "successful: %s,\n" + + "failed: %s\n" + + "}}", shards, shards - failedShards, failedShards); + + SearchResult searchResult = new SearchResult(new Gson()); + searchResult.setJsonString(json); + searchResult.setJsonObject(new JsonParser().parse(json).getAsJsonObject()); + return searchResult; + } + +}