001/*
002 * (C) Copyright 2014-2016 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Nuxeo
018 */
019
020package org.nuxeo.elasticsearch.config;
021
022import static org.nuxeo.elasticsearch.ElasticSearchConstants.ALL_FIELDS;
023import static org.nuxeo.elasticsearch.ElasticSearchConstants.BINARYTEXT_FIELD;
024import static org.nuxeo.elasticsearch.ElasticSearchConstants.DOC_TYPE;
025
026import org.nuxeo.common.xmap.annotation.XNode;
027import org.nuxeo.common.xmap.annotation.XNodeList;
028import org.nuxeo.common.xmap.annotation.XObject;
029
030/**
031 * XMap descriptor for configuring an index
032 *
033 * @author <a href="mailto:tdelprat@nuxeo.com">Tiry</a>
034 */
035@XObject(value = "elasticSearchIndex")
036public class ElasticSearchIndexConfig {
037
038    @XNode("@enabled")
039    protected boolean isEnabled = true;
040
041    @Override
042    public String toString() {
043        if (isEnabled()) {
044            return String.format("EsIndexConfig(%s, %s, %s)", getName(), getRepositoryName(), getType());
045        }
046        return "EsIndexConfig disabled";
047    }
048
049    @XNode("@name")
050    protected String name;
051
052    @XNode("@repository")
053    protected String repositoryName;
054
055    private static final String DEFAULT_REPOSITORY_NAME = "default";
056
057    @XNode("@type")
058    protected String type = DOC_TYPE;
059
060    @XNode("@create")
061    protected boolean create = true;
062
063    @XNode("settings")
064    protected String settings;
065
066    final public static String DEFAULT_SETTING = "{\n" //
067            + "   \"number_of_shards\" : 1,\n" //
068            + "   \"number_of_replicas\" : 0,\n" //
069            + "   \"analysis\" : {\n" //
070            + "      \"filter\" : {\n" //
071            + "         \"truncate_filter\" : {\n" //
072            + "            \"length\" : 256,\n" //
073            + "            \"type\" : \"truncate\"\n" //
074            + "         },\n" //
075            + "         \"en_stem_filter\" : {\n" //
076            + "            \"name\" : \"minimal_english\",\n" //
077            + "            \"type\" : \"stemmer\"\n" //
078            + "         },\n" //
079            + "         \"en_stop_filter\" : {\n" //
080            + "            \"stopwords\" : [\n" //
081            + "               \"_english_\"\n" //
082            + "            ],\n" //
083            + "            \"type\" : \"stop\"\n" //
084            + "         }\n" //
085            + "      },\n" //
086            + "      \"tokenizer\" : {\n" //
087            + "         \"path_tokenizer\" : {\n" //
088            + "            \"delimiter\" : \"/\",\n" //
089            + "            \"type\" : \"path_hierarchy\"\n" //
090            + "         }\n" + "      },\n" //
091            + "      \"analyzer\" : {\n" //
092            + "         \"en_analyzer\" : {\n" //
093            + "            \"alias\" : \"fulltext\",\n" //
094            + "            \"filter\" : [\n" //
095            + "               \"lowercase\",\n" //
096            + "               \"en_stop_filter\",\n" //
097            + "               \"en_stem_filter\",\n" //
098            + "               \"asciifolding\"\n" //
099            + "            ],\n" //
100            + "            \"type\" : \"custom\",\n" //
101            + "            \"tokenizer\" : \"standard\"\n" //
102            + "         },\n" //
103            + "         \"path_analyzer\" : {\n" //
104            + "            \"type\" : \"custom\",\n" //
105            + "            \"tokenizer\" : \"path_tokenizer\"\n" //
106            + "         },\n" //
107            + "         \"default\" : {\n" //
108            + "            \"type\" : \"custom\",\n" //
109            + "            \"tokenizer\" : \"keyword\",\n" //
110            + "            \"filter\" : [\n" //
111            + "               \"truncate_filter\"\n" //
112            + "            ]\n" //
113            + "         }\n" //
114            + "      }\n" //
115            + "   }\n" //
116            + "}";
117
118    @XNode("mapping")
119    protected String mapping;
120
121    final public static String DEFAULT_MAPPING = "{\n" //
122            + "   \"_all\" : {\n" //
123            + "      \"analyzer\" : \"fulltext\"\n" //
124            + "   },\n" //
125            + "   \"properties\" : {\n" //
126            + "      \"dc:title\" : {\n" //
127            + "         \"type\" : \"multi_field\",\n" //
128            + "         \"fields\" : {\n" //
129            + "           \"dc:title\" : {\n" //
130            + "             \"type\" : \"string\"\n" //
131            + "           },\n" //
132            + "           \"fulltext\" : {\n" //
133            + "             \"boost\": 2,\n" //
134            + "             \"type\": \"string\",\n" //
135            + "             \"analyzer\" : \"fulltext\"\n" //
136            + "          }\n" //
137            + "        }\n" //
138            + "      },\n" //
139            + "      \"dc:description\" : {\n" //
140            + "         \"type\" : \"multi_field\",\n" //
141            + "         \"fields\" : {\n" //
142            + "           \"dc:description\" : {\n" //
143            + "             \"type\" : \"string\"\n" //
144            + "           },\n" //
145            + "           \"fulltext\" : {\n" //
146            + "             \"boost\": 1.5,\n" //
147            + "             \"type\": \"string\",\n" //
148            + "             \"analyzer\" : \"fulltext\"\n" //
149            + "          }\n" //
150            + "        }\n" //
151            + "      },\n" //
152            + "      \"ecm:binarytext\" : {\n" //
153            + "         \"type\" : \"string\",\n" //
154            + "         \"index\" : \"no\",\n" //
155            + "         \"include_in_all\" : true\n" //
156            + "      },\n" //
157            + "      \"ecm:path\" : {\n" //
158            + "         \"type\" : \"multi_field\",\n" //
159            + "         \"fields\" : {\n" //
160            + "            \"children\" : {\n" //
161            + "               \"analyzer\" : \"path_analyzer\",\n" //
162            + "               \"search_analyzer\" : \"keyword\",\n" //
163            + "               \"type\" : \"string\"\n" //
164            + "            },\n" //
165            + "            \"ecm:path\" : {\n" //
166            + "               \"index\" : \"not_analyzed\",\n" //
167            + "               \"type\" : \"string\"\n" //
168            + "            }\n" //
169            + "         }\n" //
170            + "      },\n" //
171            + "      \"dc:created\": {\n" //
172            + "         \"format\": \"dateOptionalTime\",\n" //
173            + "        \"type\": \"date\"\n" //
174            + "      },\n" //
175            + "      \"dc:modified\": {\n" //
176            + "         \"format\": \"dateOptionalTime\",\n" //
177            + "        \"type\": \"date\"\n" //
178            + "      },\n" //
179            + "      \"ecm:pos*\" : {\n" //
180            + "         \"type\" : \"integer\"\n" //
181            + "      }\n" //
182            + "   }\n" //
183            + "}";
184
185    @XNodeList(value = "fetchFromSource/exclude", type = String[].class, componentType = String.class)
186    protected String[] excludes;
187
188    @XNodeList(value = "fetchFromSource/include", type = String[].class, componentType = String.class)
189    protected String[] includes;
190
191    public String[] getExcludes() {
192        if (excludes == null) {
193            return new String[] { BINARYTEXT_FIELD };
194        }
195        return excludes;
196    }
197
198    public String[] getIncludes() {
199        if (includes == null || includes.length == 0) {
200            return new String[] { ALL_FIELDS };
201        }
202        return includes;
203    }
204
205    public String getName() {
206        return name;
207    }
208
209    public String getType() {
210        return type;
211    }
212
213    public String getSettings() {
214        return settings == null ? DEFAULT_SETTING : settings;
215    }
216
217    public String getMapping() {
218        return mapping == null ? DEFAULT_MAPPING : mapping;
219    }
220
221    public boolean mustCreate() {
222        return create;
223    }
224
225    public String getRepositoryName() {
226        if (isDocumentIndex() && repositoryName == null) {
227            repositoryName = DEFAULT_REPOSITORY_NAME;
228        }
229        return repositoryName;
230    }
231
232    public boolean isEnabled() {
233        return isEnabled;
234    }
235
236    public void setEnabled(boolean isEnabled) {
237        this.isEnabled = isEnabled;
238    }
239
240    /**
241     * Return true if the index/mapping is associated with a Nuxeo document repository
242     *
243     * @since 7.4
244     */
245    public boolean isDocumentIndex() {
246        return DOC_TYPE.equals(getType());
247    }
248
249    /**
250     * Use {@code other} mapping and settings if not defined.
251     */
252    public void merge(final ElasticSearchIndexConfig other) {
253        if (other == null) {
254            return;
255        }
256        if (mapping == null && other.mapping != null) {
257            mapping = other.mapping;
258        }
259        if (settings == null && other.settings != null) {
260            settings = other.settings;
261        }
262    }
263
264}