Skip to content

Commit 9ad044b

Browse files
author
Guillaume Vaudaux-Ruth
committed
Add ability to define custom analysers
1 parent db482b4 commit 9ad044b

File tree

11 files changed

+140
-23
lines changed

11 files changed

+140
-23
lines changed

elasticsearch-annotations/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<parent>
55
<groupId>org.alien4cloud</groupId>
66
<artifactId>elasticsearch-mapping-parent</artifactId>
7-
<version>1.7.4</version>
7+
<version>1.7.5</version>
88
</parent>
99

1010
<artifactId>elasticsearch-annotations</artifactId>

elasticsearch-annotations/src/main/java/org/elasticsearch/annotation/ESObject.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,6 @@
2222
boolean source() default true;
2323

2424
boolean all() default true;
25+
26+
IndexAnalyserDefinition[] analyzerDefinitions() default {};
2527
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.elasticsearch.annotation;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Retention(RetentionPolicy.RUNTIME)
9+
@Target({ ElementType.TYPE })
10+
public @interface IndexAnalyserDefinition {
11+
String name();
12+
13+
String tokenizer() default "";
14+
15+
String[] filters() default {};
16+
17+
String type() default "";
18+
19+
String[] stopwords() default {};
20+
21+
String[] char_filter() default {};
22+
}

elasticsearch-mapping/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<parent>
55
<groupId>org.alien4cloud</groupId>
66
<artifactId>elasticsearch-mapping-parent</artifactId>
7-
<version>1.7.4</version>
7+
<version>1.7.5</version>
88
</parent>
99

1010
<artifactId>elasticsearch-mapping</artifactId>

elasticsearch-mapping/src/main/java/org/elasticsearch/mapping/MappingBuilder.java

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,24 @@
77

88
import org.elasticsearch.annotation.ESAll;
99
import org.elasticsearch.annotation.ESObject;
10+
import org.elasticsearch.annotation.IndexAnalyserDefinition;
1011
import org.elasticsearch.annotation.TypeName;
12+
import org.elasticsearch.common.collect.Maps;
13+
import org.elasticsearch.common.lang3.ArrayUtils;
1114
import org.elasticsearch.util.AnnotationScanner;
1215
import org.elasticsearch.util.MapUtil;
1316
import org.springframework.context.annotation.Scope;
1417
import org.springframework.stereotype.Component;
1518

19+
import com.fasterxml.jackson.annotation.JsonInclude;
1620
import com.fasterxml.jackson.core.JsonGenerationException;
21+
import com.fasterxml.jackson.core.JsonProcessingException;
1722
import com.fasterxml.jackson.databind.JsonMappingException;
1823
import com.fasterxml.jackson.databind.ObjectMapper;
1924

2025
/**
2126
* Helps to parse the ES annotations.
22-
*
27+
*
2328
* @author luc boutier
2429
*/
2530
@Component
@@ -28,6 +33,7 @@ public class MappingBuilder {
2833
private FieldsMappingBuilder fieldsMappingBuilder = new FieldsMappingBuilder();
2934

3035
private Map<String, String> classesMappings = new HashMap<String, String>();
36+
private Map<String, String> settingsByClassName = new HashMap<String, String>();
3137
private Map<String, String> typeByClassName = new HashMap<String, String>();
3238

3339
private Map<String, List<IFilterBuilderHelper>> filtersByClassName = new HashMap<String, List<IFilterBuilderHelper>>();
@@ -37,7 +43,7 @@ public class MappingBuilder {
3743
/**
3844
* Helper to return a valid index type from a class. Currently uses clazz.getSimpleName().toLowerCase();
3945
* Using the helper make sure this is done in a consistent way in different locations of the code.
40-
*
46+
*
4147
* @param clazz The class for which to get an index type.
4248
* @return The index type.
4349
*/
@@ -47,7 +53,7 @@ public static String indexTypeFromClass(Class<?> clazz) {
4753

4854
/**
4955
* Build mapping for the given packages.
50-
*
56+
*
5157
* @param packages list of packages in which to look for {@link ESObject} annotated classes.
5258
* @throws IntrospectionException Java reflection usage related exception.
5359
* @throws IOException In case of an IO error while creating Json.
@@ -64,7 +70,7 @@ public void initialize(String... packages) throws IntrospectionException, JsonGe
6470

6571
/**
6672
* Get the mapping json string for a given class.
67-
*
73+
*
6874
* @param clazz The class for which to get the mapping.
6975
* @return The json mapping for this class.
7076
* @throws JsonGenerationException In case the mapping failed to be serialized to json.
@@ -75,15 +81,24 @@ public void initialize(String... packages) throws IntrospectionException, JsonGe
7581
public String getMapping(Class<?> clazz) throws JsonGenerationException, JsonMappingException, IntrospectionException, IOException {
7682
String classMapping = classesMappings.get(clazz.getName());
7783
if (classMapping == null) {
78-
parseClassMapping(clazz, "");
84+
parseClassAnnotations(clazz, "");
7985
}
8086
classMapping = classesMappings.get(clazz.getName());
8187
return classMapping;
8288
}
8389

90+
public String getIndexSettings(Class<?> clazz) throws JsonGenerationException, JsonMappingException, IntrospectionException, IOException {
91+
String settings = settingsByClassName.get(clazz.getName());
92+
if (settings == null) {
93+
parseClassAnnotations(clazz, "");
94+
}
95+
settings = settingsByClassName.get(clazz.getName());
96+
return settings;
97+
}
98+
8499
/**
85100
* Get the name of the type in elastic search for the given class.
86-
*
101+
*
87102
* @param clazz The class for which to get the type.
88103
* @return The type name in elastic search.
89104
*/
@@ -93,7 +108,7 @@ public String getTypeName(Class<?> clazz) {
93108

94109
/**
95110
* Get the list of es fields that should be filtered in a filter search for the given class.
96-
*
111+
*
97112
* @param clazz The class for which to get the facets.
98113
* @return The list of filters builders for this class.
99114
*/
@@ -103,7 +118,7 @@ public List<IFilterBuilderHelper> getFilters(Class<?> clazz) {
103118

104119
/**
105120
* Get the list of es fields that should be filtered in a filter search for the given class.
106-
*
121+
*
107122
* @param className The name for which to get the facets.
108123
* @return The list of filters builders for this class.
109124
*/
@@ -113,7 +128,7 @@ public List<IFilterBuilderHelper> getFilters(String className) {
113128

114129
/**
115130
* Get the list of es fields that should be faceted in a facet search for the given class.
116-
*
131+
*
117132
* @param clazz The class for which to get the facets.
118133
* @return The list of facet builders for this class.
119134
*/
@@ -123,7 +138,7 @@ public List<IFacetBuilderHelper> getFacets(Class<?> clazz) {
123138

124139
/**
125140
* Get the list of es fields that should be faceted in a facet search for the given class.
126-
*
141+
*
127142
* @param className The name for which to get the facets.
128143
* @return The list of facet builders for this class.
129144
*/
@@ -133,7 +148,7 @@ public List<IFacetBuilderHelper> getFacets(String className) {
133148

134149
/**
135150
* Get the {@link SourceFetchContext} for a given fetch context.
136-
*
151+
*
137152
* @param className The class for which to get the fetch context.
138153
* @param fetchContext The fetch context for which to get the field list.
139154
* @return The requested {@link SourceFetchContext} or null if no context match the given class and fetch context key.
@@ -149,11 +164,12 @@ public SourceFetchContext getFetchSource(String className, String fetchContext)
149164
private void initialize(String packageName) throws IntrospectionException, JsonGenerationException, JsonMappingException, IOException {
150165
Set<Class<?>> classSet = org.elasticsearch.util.AnnotationScanner.scan(packageName, ESObject.class);
151166
for (Class<?> clazz : classSet) {
152-
parseClassMapping(clazz, "");
167+
parseClassAnnotations(clazz, "");
153168
}
154169
}
155170

156-
public void parseClassMapping(Class<?> clazz, String pathPrefix) throws IntrospectionException, JsonGenerationException, JsonMappingException, IOException {
171+
public void parseClassAnnotations(Class<?> clazz, String pathPrefix)
172+
throws IntrospectionException, JsonGenerationException, JsonMappingException, IOException {
157173
ESObject esObject = AnnotationScanner.getAnnotation(ESObject.class, clazz);
158174
ESAll esAll = AnnotationScanner.getAnnotation(ESAll.class, clazz);
159175

@@ -196,5 +212,35 @@ public void parseClassMapping(Class<?> clazz, String pathPrefix) throws Introspe
196212
this.facetByClassName.put(clazz.getName(), facetFields);
197213
this.filtersByClassName.put(clazz.getName(), filteredFields);
198214
this.fetchSourceContextByClass.put(clazz.getName(), fetchContexts);
215+
216+
this.settingsByClassName.put(clazz.getName(), buildSettings(mapper, esObject.analyzerDefinitions()));
217+
}
218+
219+
@JsonInclude(JsonInclude.Include.NON_EMPTY)
220+
private static class AnalyserFields {
221+
public String tokenizer;
222+
public String type;
223+
public String[] char_filter;
224+
public String[] filter;
225+
public String[] stopwords;
226+
}
227+
228+
private String buildSettings(ObjectMapper mapper, IndexAnalyserDefinition[] customAnalyserDefinitions) throws JsonProcessingException {
229+
if (ArrayUtils.isEmpty(customAnalyserDefinitions)) {
230+
return null;
231+
}
232+
233+
Map<Object, Object> analysers = Maps.newHashMap();
234+
for (IndexAnalyserDefinition analyserDefinition : customAnalyserDefinitions) {
235+
AnalyserFields analyserFields = new AnalyserFields();
236+
analyserFields.char_filter = analyserDefinition.char_filter();
237+
analyserFields.filter = analyserDefinition.filters();
238+
analyserFields.stopwords = analyserDefinition.stopwords();
239+
analyserFields.tokenizer = analyserDefinition.tokenizer();
240+
analyserFields.type = analyserDefinition.type();
241+
analysers.put(analyserDefinition.name(), analyserFields);
242+
}
243+
244+
return "{\"analysis\":{\"analyzer\":" + mapper.writeValueAsString(analysers) + "}}";
199245
}
200246
}

elasticsearch-mapping/src/test/java/org/elasticsearch/mapping/MappingBuilderTest.java

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,46 @@
66
import java.io.IOException;
77
import java.nio.file.Paths;
88

9+
import org.elasticsearch.mapping.model.City;
910
import org.elasticsearch.mapping.model.Person;
11+
import org.junit.Assert;
12+
import org.junit.Before;
1013
import org.junit.Test;
1114

12-
import junit.framework.Assert;
13-
1415
/**
1516
* Test the mappings.
1617
*
1718
* @author luc boutier
1819
*/
1920
public class MappingBuilderTest {
2021

22+
private MappingBuilder mappingBuilder;
23+
24+
@Before
25+
public void setUp() throws IntrospectionException, IOException {
26+
mappingBuilder = new MappingBuilder();
27+
mappingBuilder.initialize("org.elasticsearch.mapping.model");
28+
}
29+
2130
@Test
2231
public void testSimpleClassMapping() throws IntrospectionException, IOException {
23-
MappingBuilder mappingBuilder = new MappingBuilder();
24-
mappingBuilder.initialize("org.elasticsearch.mapping.model");
2532
String personMapping = mappingBuilder.getMapping(Person.class);
26-
BufferedReader brTest = new BufferedReader(new FileReader(Paths.get("src/test/resources/mapping.json").toFile()));
27-
String expected = brTest.readLine();
28-
Assert.assertEquals(expected, personMapping);
33+
assertSameContent(personMapping, "src/test/resources/person-mapping.json");
2934
}
35+
36+
@Test
37+
public void testSettingsAndMapping() throws IntrospectionException, IOException {
38+
String citySettings = mappingBuilder.getIndexSettings(City.class);
39+
assertSameContent(citySettings, "src/test/resources/city-settings.json");
40+
41+
String cityMapping = mappingBuilder.getMapping(City.class);
42+
assertSameContent(cityMapping, "src/test/resources/city-mapping.json");
43+
}
44+
45+
private void assertSameContent(String content, String expectedContentFromFile) throws IOException {
46+
BufferedReader brMappingTest = new BufferedReader(new FileReader(Paths.get(expectedContentFromFile).toFile()));
47+
String expectedMapping = brMappingTest.readLine();
48+
Assert.assertEquals(expectedMapping, content);
49+
}
50+
3051
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.elasticsearch.mapping.model;
2+
3+
import org.elasticsearch.annotation.ESObject;
4+
import org.elasticsearch.annotation.IndexAnalyserDefinition;
5+
import org.elasticsearch.annotation.StringField;
6+
import org.elasticsearch.annotation.StringFieldMulti;
7+
import org.elasticsearch.mapping.IndexType;
8+
9+
@ESObject(analyzerDefinitions = @IndexAnalyserDefinition(name = "lowerCaseAnalyser", filters = "lowercase", tokenizer = "keyword"))
10+
public class City {
11+
12+
@StringFieldMulti(main = @StringField(indexType = IndexType.not_analyzed),
13+
multiNames = "lower_case",
14+
multi = @StringField(indexType = IndexType.analyzed, analyser = "lowerCaseAnalyser", includeInAll = false))
15+
private String city;
16+
17+
public String getCity() {
18+
return city;
19+
}
20+
21+
public void setCity(String city) {
22+
this.city = city;
23+
}
24+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"city":{"_type":{"index":"not_analyzed","store":false},"_source":{"enabled":true},"_all":{"enabled":true},"properties":{"city":{"include_in_all":true,"term_vector":"no","index":"not_analyzed","boost":1.0,"store":false,"type":"string","fields":{"lower_case":{"include_in_all":false,"analyzer":"lowerCaseAnalyser","term_vector":"no","index":"analyzed","boost":1.0,"store":false,"type":"string"}}}}}}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"analysis":{"analyzer":{"lowerCaseAnalyser":{"tokenizer":"keyword","filters":["lowercase"]}}}}

0 commit comments

Comments
 (0)