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