diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Setting.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Setting.java new file mode 100644 index 000000000..c2d746f0f --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Setting.java @@ -0,0 +1,36 @@ +/* + * Copyright 2014 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.data.elasticsearch.annotations; + +import java.lang.annotation.*; + +import org.springframework.data.annotation.Persistent; + +/** + * Elasticsearch Setting + * + * @author Mohsin Husen + */ + +@Persistent +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface Setting { + + String settingPath() default ""; + +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java index b0c08dd29..1eff8c7ae 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java @@ -23,7 +23,9 @@ import static org.elasticsearch.common.collect.Sets.*; import static org.elasticsearch.index.VersionType.*; import static org.springframework.data.elasticsearch.core.MappingBuilder.*; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; import java.lang.reflect.Method; import java.util.*; @@ -64,10 +66,14 @@ import org.elasticsearch.search.facet.FacetBuilder; import org.elasticsearch.search.highlight.HighlightBuilder; import org.elasticsearch.search.sort.SortBuilder; import org.elasticsearch.search.sort.SortOrder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.ClassPathResource; import org.springframework.data.domain.Page; import org.springframework.data.domain.Sort; import org.springframework.data.elasticsearch.ElasticsearchException; import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.annotations.Setting; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter; import org.springframework.data.elasticsearch.core.facet.FacetRequest; @@ -87,6 +93,7 @@ import org.springframework.util.Assert; public class ElasticsearchTemplate implements ElasticsearchOperations { + private static final Logger logger = LoggerFactory.getLogger(ElasticsearchTemplate.class); private Client client; private ElasticsearchConverter elasticsearchConverter; private ResultsMapper resultsMapper; @@ -554,6 +561,17 @@ public class ElasticsearchTemplate implements ElasticsearchOperations { } private boolean createIndexWithSettings(Class clazz) { + if(clazz.isAnnotationPresent(Setting.class)) { + String settingPath = clazz.getAnnotation(Setting.class).settingPath(); + if(isNotBlank(settingPath)) { + String settings = readFileFromClasspath(settingPath); + if(isNotBlank(settings)) { + return createIndex(getPersistentEntityFor(clazz).getIndexName(), settings); + } + } else { + logger.info("settingPath in @Setting has to be defined. Using default instead."); + } + } return createIndex(getPersistentEntityFor(clazz).getIndexName(), getDefaultSettings(getPersistentEntityFor(clazz))); } @@ -790,4 +808,33 @@ public class ElasticsearchTemplate implements ElasticsearchOperations { private boolean isDocument(Class clazz) { return clazz.isAnnotationPresent(Document.class); } + + public static String readFileFromClasspath(String url) { + StringBuilder stringBuilder = new StringBuilder(); + + BufferedReader bufferedReader = null; + + try { + ClassPathResource classPathResource = new ClassPathResource(url); + InputStreamReader inputStreamReader = new InputStreamReader(classPathResource.getInputStream()); + bufferedReader = new BufferedReader(inputStreamReader); + String line; + + while ((line = bufferedReader.readLine()) != null) { + stringBuilder.append(line); + } + } catch (Exception e) { + logger.debug(String.format("Failed to load file from url: %s: %s", url, e.getMessage())); + return null; + } finally { + if (bufferedReader != null) + try { + bufferedReader.close(); + } catch (IOException e) { + logger.debug(String.format("Unable to close buffered reader.. %s", e.getMessage())); + } + } + + return stringBuilder.toString(); + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java index 78259f02d..395685fe8 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2014 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. @@ -43,4 +43,6 @@ public interface ElasticsearchPersistentEntity extends PersistentEntity extends BasicPersistentEntit private String indexStoreType; private String parentType; private ElasticsearchPersistentProperty parentIdProperty; + private String settingPath; public SimpleElasticsearchPersistentEntity(TypeInformation typeInformation) { super(typeInformation); @@ -66,6 +68,9 @@ public class SimpleElasticsearchPersistentEntity extends BasicPersistentEntit this.refreshInterval = typeInformation.getType().getAnnotation(Document.class).refreshInterval(); this.indexStoreType = typeInformation.getType().getAnnotation(Document.class).indexStoreType(); } + if(clazz.isAnnotationPresent(Setting.class)) { + this.settingPath = typeInformation.getType().getAnnotation(Setting.class).settingPath(); + } } @Override @@ -115,6 +120,11 @@ public class SimpleElasticsearchPersistentEntity extends BasicPersistentEntit return parentIdProperty; } + @Override + public String settingPath() { + return settingPath; + } + @Override public void addPersistentProperty(ElasticsearchPersistentProperty property) { super.addPersistentProperty(property); diff --git a/src/test/java/org/springframework/data/elasticsearch/entities/SettingEntity.java b/src/test/java/org/springframework/data/elasticsearch/entities/SettingEntity.java new file mode 100644 index 000000000..9d83acd0f --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/entities/SettingEntity.java @@ -0,0 +1,62 @@ +/* + * Copyright 2014 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.data.elasticsearch.entities; + +import org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.FieldType; +import org.springframework.data.elasticsearch.annotations.Setting; + +/** + * Sample SettingEntity for test out dynamic setting using @Setting Annotation + * + * @author Mohsin Husen + */ +@Setting(settingPath = "/settings/test-settings.json") +@Document(indexName = "test-setting-index", type = "test-setting-type") +public class SettingEntity { + + @Id + private String id; + private String name; + @Field(type = FieldType.String, searchAnalyzer = "emailAnalyzer", indexAnalyzer = "emailAnalyzer") + private String email; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/setting/SettingEntityRepository.java b/src/test/java/org/springframework/data/elasticsearch/repositories/setting/SettingEntityRepository.java new file mode 100644 index 000000000..91a88378e --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/setting/SettingEntityRepository.java @@ -0,0 +1,28 @@ +/* + * Copyright 2014 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.data.elasticsearch.repositories.setting; + +import org.springframework.data.elasticsearch.entities.SettingEntity; +import org.springframework.data.elasticsearch.repository.ElasticsearchCrudRepository; + +/** + * SettingEntityRepository + * + * @author Mohsin Husen + */ +public interface SettingEntityRepository extends ElasticsearchCrudRepository{ + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/setting/SettingEntityRepositoryTest.java b/src/test/java/org/springframework/data/elasticsearch/repositories/setting/SettingEntityRepositoryTest.java new file mode 100644 index 000000000..d4f4aa395 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/setting/SettingEntityRepositoryTest.java @@ -0,0 +1,112 @@ +/* + * Copyright 2014 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.data.elasticsearch.repositories.setting; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertThat; + +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.RandomStringUtils; +import org.elasticsearch.index.query.QueryBuilders; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; +import org.springframework.data.elasticsearch.core.query.SearchQuery; +import org.springframework.data.elasticsearch.entities.SettingEntity; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * SettingEntityRepositoryTest + * + * @author Mohsin Husen + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("classpath:dynamic-settings-test.xml") +public class SettingEntityRepositoryTest { + + @Autowired + private SettingEntityRepository repository; + + @Autowired + private ElasticsearchTemplate elasticsearchTemplate; + + @Before + public void before() { + elasticsearchTemplate.deleteIndex(SettingEntity.class); + elasticsearchTemplate.createIndex(SettingEntity.class); + elasticsearchTemplate.putMapping(SettingEntity.class); + elasticsearchTemplate.refresh(SettingEntity.class, true); + } + + /* + DATAES-64 + */ + @Test + public void shouldCreateGivenDynamicSettingsForGivenIndex() { + //given + //delete , create and apply mapping in before method + + // then + assertThat(elasticsearchTemplate.indexExists(SettingEntity.class), is(true)); + Map map = elasticsearchTemplate.getSetting(SettingEntity.class); + assertThat(map.containsKey("index.number_of_replicas"), is(true)); + assertThat(map.containsKey("index.number_of_shards"), is(true)); + assertThat(map.containsKey("index.analysis.analyzer.emailAnalyzer.tokenizer"), is(true)); + assertThat((String) map.get("index.number_of_replicas"), is("0")); + assertThat((String) map.get("index.number_of_shards"), is("1")); + assertThat((String) map.get("index.analysis.analyzer.emailAnalyzer.tokenizer"), is("uax_url_email")); + } + + /* + DATAES-64 + */ + @Test + public void shouldSearchOnGivenTokenizerUsingGivenDynamicSettingsForGivenIndex() { + //given + SettingEntity settingEntity1 = new SettingEntity(); + settingEntity1.setId(RandomStringUtils.randomNumeric(5)); + settingEntity1.setName("test-setting1"); + settingEntity1.setEmail("test_setting1@test.com"); + + repository.save(settingEntity1); + + SettingEntity settingEntity2 = new SettingEntity(); + settingEntity2.setId(RandomStringUtils.randomNumeric(5)); + settingEntity2.setName("test-setting2"); + settingEntity2.setEmail("test_setting2@test.com"); + + repository.save(settingEntity2); + + //when + SearchQuery searchQuery = new NativeSearchQueryBuilder() + .withQuery(QueryBuilders.termQuery("email", settingEntity1.getEmail())).build(); + + long count = elasticsearchTemplate.count(searchQuery, SettingEntity.class); + List entityList = elasticsearchTemplate.queryForList(searchQuery, SettingEntity.class); + + //then + assertThat(count, is(1L)); + assertThat(entityList, is(notNullValue())); + assertThat(entityList.size(), is(1)); + assertThat(entityList.get(0).getEmail(), is(settingEntity1.getEmail())); + } +} diff --git a/src/test/resources/dynamic-settings-test.xml b/src/test/resources/dynamic-settings-test.xml new file mode 100644 index 000000000..6b1212a52 --- /dev/null +++ b/src/test/resources/dynamic-settings-test.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + diff --git a/src/test/resources/settings/test-settings.json b/src/test/resources/settings/test-settings.json new file mode 100644 index 000000000..893b0a7a1 --- /dev/null +++ b/src/test/resources/settings/test-settings.json @@ -0,0 +1,14 @@ +{ + "index": { + "number_of_shards" : "1", + "number_of_replicas" : "0", + "analysis" :{ + "analyzer": { + "emailAnalyzer": { + "type" : "custom", + "tokenizer" : "uax_url_email" + } + } + } + } +}