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 java.io.File;
027import java.io.FileInputStream;
028import java.io.FileNotFoundException;
029import java.io.IOException;
030import java.io.InputStream;
031
032import org.apache.commons.io.IOUtils;
033import org.apache.commons.lang.StringUtils;
034import org.nuxeo.common.Environment;
035import org.nuxeo.common.xmap.annotation.XNode;
036import org.nuxeo.common.xmap.annotation.XNodeList;
037import org.nuxeo.common.xmap.annotation.XObject;
038import org.nuxeo.elasticsearch.core.IncrementalIndexNameGenerator;
039
040/**
041 * XMap descriptor for configuring an index
042 *
043 * @author <a href="mailto:tdelprat@nuxeo.com">Tiry</a>
044 */
045@XObject(value = "elasticSearchIndex")
046public class ElasticSearchIndexConfig {
047    public static final String DEFAULT_SETTING_FILE = "default-doc-settings.json";
048
049    public static final String DEFAULT_MAPPING_FILE = "default-doc-mapping.json";
050
051    protected static final String DEFAULT_REPOSITORY_NAME = "default";
052
053    protected static final String WRITE_SUFFIX = "-write";
054
055    @XNode("@enabled")
056    protected boolean isEnabled = true;
057
058    @XNode("@name")
059    protected String name;
060
061    // @since 9.3
062    @XNode("@manageAlias")
063    protected boolean manageAlias;
064
065    // @since 9.3
066    @XNode("@writeAlias")
067    protected String writeAlias;
068
069    @XNode("@repository")
070    protected String repositoryName;
071
072    @XNode("@type")
073    protected String type = DOC_TYPE;
074
075    @XNode("@create")
076    protected boolean create = true;
077
078    @XNode("settings")
079    protected String settings;
080
081    @XNode("settings@file")
082    protected String settingsFile;
083
084    @XNode("mapping")
085    protected String mapping;
086
087    @XNode("mapping@file")
088    protected String mappingFile;
089
090    @XNodeList(value = "fetchFromSource/exclude", type = String[].class, componentType = String.class)
091    protected String[] excludes;
092
093    @XNodeList(value = "fetchFromSource/include", type = String[].class, componentType = String.class)
094    protected String[] includes;
095
096    @Override
097    public String toString() {
098        if (isEnabled()) {
099            return String.format("EsIndexConfig(%s, %s, %s)", getName(), getRepositoryName(), getType());
100        }
101        return "EsIndexConfig disabled";
102    }
103
104    public String[] getExcludes() {
105        if (excludes == null) {
106            return new String[] { BINARYTEXT_FIELD };
107        }
108        return excludes;
109    }
110
111    public String[] getIncludes() {
112        if (includes == null || includes.length == 0) {
113            return new String[] { ALL_FIELDS };
114        }
115        return includes;
116    }
117
118    public String getName() {
119        return name;
120    }
121
122    public String getType() {
123        return type;
124    }
125
126    public String getSettings() {
127        if (settingsFile != null) {
128            return contentOfFile(settingsFile);
129        } else if (settings != null && !settings.isEmpty()) {
130            return settings;
131        }
132        return contentOfFile(DEFAULT_SETTING_FILE);
133    }
134
135    protected String contentOfFile(String filename) {
136        try {
137            return IOUtils.toString(getResourceStream(filename), "UTF-8");
138        } catch (IOException e) {
139            throw new IllegalArgumentException("Cannot load resource file: " + filename, e);
140        }
141    }
142
143    protected InputStream getResourceStream(String filename) {
144        // First check if the resource is available on the config directory
145        File file = new File(Environment.getDefault().getConfig(), filename);
146        if (file.exists()) {
147            try {
148                return new FileInputStream(file);
149            } catch (FileNotFoundException e) {
150                // try another way
151            }
152        }
153
154        // getResourceAsStream is needed getResource will not work when called from another module
155        InputStream ret = this.getClass().getClassLoader().getResourceAsStream(filename);
156        if (ret == null) {
157            // Then try to get it from jar
158            ret = this.getClass().getClassLoader().getResourceAsStream(filename);
159        }
160        if (ret == null) {
161            throw new IllegalArgumentException(
162                    String.format("Resource file cannot be found: %s or %s", file.getAbsolutePath(), filename));
163        }
164        return ret;
165    }
166
167    public String getMapping() {
168        if (mappingFile != null) {
169            return contentOfFile(mappingFile);
170        } else if (mapping != null && !mapping.isEmpty()) {
171            return mapping;
172        }
173        return contentOfFile(DEFAULT_MAPPING_FILE);
174    }
175
176    public boolean mustCreate() {
177        return create;
178    }
179
180    public String getRepositoryName() {
181        if (isDocumentIndex() && repositoryName == null) {
182            repositoryName = DEFAULT_REPOSITORY_NAME;
183        }
184        return repositoryName;
185    }
186
187    public boolean isEnabled() {
188        return isEnabled;
189    }
190
191    public void setEnabled(boolean isEnabled) {
192        this.isEnabled = isEnabled;
193    }
194
195    /**
196     * Return true if the index/mapping is associated with a Nuxeo document repository
197     *
198     * @since 7.4
199     */
200    public boolean isDocumentIndex() {
201        return DOC_TYPE.equals(getType());
202    }
203
204    /**
205     * Use {@code other} mapping and settings if not defined.
206     */
207    public void merge(final ElasticSearchIndexConfig other) {
208        if (other == null) {
209            return;
210        }
211        if (mapping == null && other.mapping != null) {
212            mapping = other.mapping;
213        }
214        if (settings == null && other.settings != null) {
215            settings = other.settings;
216        }
217        if (mappingFile == null && other.mappingFile != null) {
218            mappingFile = other.mappingFile;
219        }
220        if (settingsFile == null && other.settingsFile != null) {
221            settingsFile = other.settingsFile;
222        }
223    }
224
225    // @since 9.3
226    public boolean hasExplicitWriteIndex() {
227        return StringUtils.isNotBlank(writeAlias);
228    }
229
230    // @since 9.3
231    public String writeIndexOrAlias() {
232        // Custom alias managed outside of Nuxeo
233        if (hasExplicitWriteIndex()) {
234            return writeAlias;
235        }
236        // Nuxeo manages the write alias
237        if (manageAlias) {
238            return name + WRITE_SUFFIX;
239        }
240        // Simple index
241        return name;
242    }
243
244    // @since 9.3
245    public boolean manageAlias() {
246        return manageAlias;
247    }
248
249    // @since 9.3
250    public String newWriteIndexForAlias(String aliasName, String oldIndexName) {
251        // TODO make the alias resolver configurable
252        return new IncrementalIndexNameGenerator().getNextIndexName(aliasName, oldIndexName);
253    }
254}