/*
* Licensed to Elastic Search and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Elastic Search licenses this
* file to you 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.elasticsearch.cluster.metadata;
import com.google.common.collect.Maps;
import org.elasticsearch.ElasticSearchException;
import org.elasticsearch.ElasticSearchIllegalArgumentException;
import org.elasticsearch.cluster.ClusterService;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.ProcessedClusterStateUpdateTask;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.indices.IndexTemplateAlreadyExistsException;
import org.elasticsearch.indices.IndexTemplateMissingException;
import org.elasticsearch.indices.InvalidIndexTemplateException;
import java.util.Map;
/**
*
*/
public class MetaDataIndexTemplateService extends AbstractComponent {
private final ClusterService clusterService;
@Inject
public MetaDataIndexTemplateService(Settings settings, ClusterService clusterService) {
super(settings);
this.clusterService = clusterService;
}
public void removeTemplate(final RemoveRequest request, final RemoveListener listener) {
clusterService.submitStateUpdateTask("remove-index-template [" + request.name + "]", new ProcessedClusterStateUpdateTask() {
@Override
public ClusterState execute(ClusterState currentState) {
if (!currentState.metaData().templates().containsKey(request.name)) {
listener.onFailure(new IndexTemplateMissingException(request.name));
return currentState;
}
MetaData.Builder metaData = MetaData.builder().metaData(currentState.metaData())
.removeTemplate(request.name);
return ClusterState.builder().state(currentState).metaData(metaData).build();
}
@Override
public void clusterStateProcessed(ClusterState clusterState) {
listener.onResponse(new RemoveResponse(true));
}
});
}
public void putTemplate(final PutRequest request, final PutListener listener) {
ImmutableSettings.Builder updatedSettingsBuilder = ImmutableSettings.settingsBuilder();
for (Map.Entry<String, String> entry : request.settings.getAsMap().entrySet()) {
if (!entry.getKey().startsWith("index.")) {
updatedSettingsBuilder.put("index." + entry.getKey(), entry.getValue());
} else {
updatedSettingsBuilder.put(entry.getKey(), entry.getValue());
}
}
request.settings(updatedSettingsBuilder.build());
if (request.name == null) {
listener.onFailure(new ElasticSearchIllegalArgumentException("index_template must provide a name"));
return;
}
if (request.template == null) {
listener.onFailure(new ElasticSearchIllegalArgumentException("index_template must provide a template"));
return;
}
try {
validate(request);
} catch (Exception e) {
listener.onFailure(e);
return;
}
IndexTemplateMetaData.Builder templateBuilder;
try {
templateBuilder = IndexTemplateMetaData.builder(request.name);
templateBuilder.order(request.order);
templateBuilder.template(request.template);
templateBuilder.settings(request.settings);
for (Map.Entry<String, String> entry : request.mappings.entrySet()) {
templateBuilder.putMapping(entry.getKey(), entry.getValue());
}
for (Map.Entry<String, IndexMetaData.Custom> entry : request.customs.entrySet()) {
templateBuilder.putCustom(entry.getKey(), entry.getValue());
}
} catch (Exception e) {
listener.onFailure(e);
return;
}
final IndexTemplateMetaData template = templateBuilder.build();
clusterService.submitStateUpdateTask("create-index-template [" + request.name + "], cause [" + request.cause + "]", new ProcessedClusterStateUpdateTask() {
@Override
public ClusterState execute(ClusterState currentState) {
if (request.create && currentState.metaData().templates().containsKey(request.name)) {
listener.onFailure(new IndexTemplateAlreadyExistsException(request.name));
return currentState;
}
MetaData.Builder builder = MetaData.builder().metaData(currentState.metaData())
.put(template);
return ClusterState.builder().state(currentState).metaData(builder).build();
}
@Override
public void clusterStateProcessed(ClusterState clusterState) {
listener.onResponse(new PutResponse(true, template));
}
});
}
private void validate(PutRequest request) throws ElasticSearchException {
if (request.name.contains(" ")) {
throw new InvalidIndexTemplateException(request.name, "name must not contain a space");
}
if (request.name.contains(",")) {
throw new InvalidIndexTemplateException(request.name, "name must not contain a ','");
}
if (request.name.contains("#")) {
throw new InvalidIndexTemplateException(request.name, "name must not contain a '#'");
}
if (request.name.startsWith("_")) {
throw new InvalidIndexTemplateException(request.name, "name must not start with '_'");
}
if (!request.name.toLowerCase().equals(request.name)) {
throw new InvalidIndexTemplateException(request.name, "name must be lower cased");
}
if (request.template.contains(" ")) {
throw new InvalidIndexTemplateException(request.name, "template must not contain a space");
}
if (request.template.contains(",")) {
throw new InvalidIndexTemplateException(request.name, "template must not contain a ','");
}
if (request.template.contains("#")) {
throw new InvalidIndexTemplateException(request.name, "template must not contain a '#'");
}
if (request.template.startsWith("_")) {
throw new InvalidIndexTemplateException(request.name, "template must not start with '_'");
}
if (!Strings.validFileNameExcludingAstrix(request.template)) {
throw new InvalidIndexTemplateException(request.name, "template must not container the following characters " + Strings.INVALID_FILENAME_CHARS);
}
}
public static interface PutListener {
void onResponse(PutResponse response);
void onFailure(Throwable t);
}
public static class PutRequest {
final String name;
final String cause;
boolean create;
int order;
String template;
Settings settings = ImmutableSettings.Builder.EMPTY_SETTINGS;
Map<String, String> mappings = Maps.newHashMap();
Map<String, IndexMetaData.Custom> customs = Maps.newHashMap();
public PutRequest(String cause, String name) {
this.cause = cause;
this.name = name;
}
public PutRequest order(int order) {
this.order = order;
return this;
}
public PutRequest template(String template) {
this.template = template;
return this;
}
public PutRequest create(boolean create) {
this.create = create;
return this;
}
public PutRequest settings(Settings settings) {
this.settings = settings;
return this;
}
public PutRequest mappings(Map<String, String> mappings) {
this.mappings.putAll(mappings);
return this;
}
public PutRequest customs(Map<String, IndexMetaData.Custom> customs) {
this.customs.putAll(customs);
return this;
}
public PutRequest putMapping(String mappingType, String mappingSource) {
mappings.put(mappingType, mappingSource);
return this;
}
}
public static class PutResponse {
private final boolean acknowledged;
private final IndexTemplateMetaData template;
public PutResponse(boolean acknowledged, IndexTemplateMetaData template) {
this.acknowledged = acknowledged;
this.template = template;
}
public boolean acknowledged() {
return acknowledged;
}
public IndexTemplateMetaData template() {
return template;
}
}
public static class RemoveRequest {
final String name;
public RemoveRequest(String name) {
this.name = name;
}
}
public static class RemoveResponse {
private final boolean acknowledged;
public RemoveResponse(boolean acknowledged) {
this.acknowledged = acknowledged;
}
public boolean acknowledged() {
return acknowledged;
}
}
public static interface RemoveListener {
void onResponse(RemoveResponse response);
void onFailure(Throwable t);
}
}