diff --git a/.gitignore b/.gitignore
index 9b08022f..4076a06d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,5 @@
/.classpath
/.settings
/target
+*.iml
+*.idea
diff --git a/README.textile b/README.textile
index 24cddb12..c791c6fd 100644
--- a/README.textile
+++ b/README.textile
@@ -160,7 +160,7 @@ If merging fails, the factory will not start.
h3. Force rebuild mappings (use with caution)
For test purpose or for continuous integration, you could force the factory to clean the previous @type@ when starting the client.
-It will *remove all your datas* for that @type@. Just set @forceReinit@ property to @true@.
+It will *remove all your datas* for that @type@. Just set @forceMapping@ property to @true@.
bc.
@@ -169,5 +169,5 @@ bc. twitter/tweet
-
+
diff --git a/pom.xml b/pom.xml
index 5cd8a9f5..0b51d483 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,7 +27,7 @@
3.1.1.RELEASE
- 0.19.1
+ 0.19.3-SNAPSHOT
diff --git a/src/main/java/fr/pilato/spring/elasticsearch/ElasticsearchAbstractClientFactoryBean.java b/src/main/java/fr/pilato/spring/elasticsearch/ElasticsearchAbstractClientFactoryBean.java
index 60ca6afb..208ca0a9 100644
--- a/src/main/java/fr/pilato/spring/elasticsearch/ElasticsearchAbstractClientFactoryBean.java
+++ b/src/main/java/fr/pilato/spring/elasticsearch/ElasticsearchAbstractClientFactoryBean.java
@@ -12,21 +12,23 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesResponse;
-import org.elasticsearch.action.admin.indices.close.CloseIndexRequestBuilder;
-import org.elasticsearch.action.admin.indices.close.CloseIndexResponse;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse;
import org.elasticsearch.action.admin.indices.open.OpenIndexRequestBuilder;
import org.elasticsearch.action.admin.indices.open.OpenIndexResponse;
import org.elasticsearch.action.admin.indices.settings.UpdateSettingsRequestBuilder;
+import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateResponse;
+import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.IndexMetaData;
+import org.elasticsearch.cluster.metadata.IndexTemplateMetaData;
import org.elasticsearch.cluster.metadata.MappingMetaData;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
+import org.springframework.util.Assert;
/**
* An abstract {@link FactoryBean} used to create an ElasticSearch
@@ -79,8 +81,14 @@
* alltheworld:rss
*
*
- *
+ *
+ *
+ * rss_template
+ *
+ *
+ *
*
+ *
*
*
*
@@ -131,7 +139,9 @@ public abstract class ElasticsearchAbstractClientFactoryBean extends Elasticsear
protected Client client;
- protected boolean forceReinit;
+ protected boolean forceMapping;
+
+ protected boolean forceTemplate;
protected boolean mergeMapping;
@@ -141,6 +151,8 @@ public abstract class ElasticsearchAbstractClientFactoryBean extends Elasticsear
protected String[] aliases;
+ protected String[] templates;
+
protected String classpathRoot = "/es";
// TODO Let the user decide
@@ -158,12 +170,21 @@ public abstract class ElasticsearchAbstractClientFactoryBean extends Elasticsear
/**
* Set to true if you want to force reinit indexes/mapping
- * @param forceReinit
+ * @param forceMapping
*/
- public void setForceReinit(boolean forceReinit) {
- this.forceReinit = forceReinit;
+ public void setForceMapping(boolean forceMapping) {
+ this.forceMapping = forceMapping;
}
+ /**
+ * Set to true if you want to force recreate templates
+ *
+ * @param forceTemplate
+ */
+ public void setForceTemplate(boolean forceTemplate) {
+ this.forceTemplate = forceTemplate;
+ }
+
/**
* Set to true if you want to try to merge mappings
* @param mergeMapping
@@ -220,6 +241,28 @@ public void setMappings(String[] mappings) {
public void setAliases(String[] aliases) {
this.aliases = aliases;
}
+
+ /**
+ * Define templates you want to manage with this factory
+ *
+ * Example :
+ *
+ *
+ * {@code
+ *
+ *
+ * template_1
+ * template_2
+ *
+ *
+ * }
+ *
+ *
+ * @param templates list of template
+ */
+ public void setTemplates(String[] templates) {
+ this.templates = templates;
+ }
/**
* Classpath root for index and mapping files (default : /es)
@@ -244,6 +287,7 @@ public void afterPropertiesSet() throws Exception {
logger.info("Starting ElasticSearch client");
client = buildClient();
+ initTemplates();
initMappings();
initAliases();
}
@@ -256,7 +300,7 @@ public void destroy() throws Exception {
client.close();
}
} catch (final Exception e) {
- logger.error("Error closing Elasticsearch client: ", e);
+ logger.error("Error closing ElasticSearch client: ", e);
}
}
@@ -275,9 +319,29 @@ public boolean isSingleton() {
return true;
}
+ /**
+ * Init templates if needed.
+ *
+ * Note that you can force to recreate template using
+ * {@link #setForceTemplate(boolean)}
+ *
+ * @throws Exception
+ */
+ private void initTemplates() throws Exception {
+ if (templates != null && templates.length > 0) {
+ for (int i = 0; i < templates.length; i++) {
+ String template = templates[i];
+ Assert.hasText(template, "Can not read template in ["
+ + templates[i]
+ + "]. Check that templates is not empty.");
+ createTemplate(template, forceTemplate);
+ }
+ }
+ }
+
/**
* Init mapping if needed.
- *
Note that you can force to reinit mapping using {@link #setForceReinit(boolean)}
+ *
Note that you can force to reinit mapping using {@link #setForceMapping(boolean)}
* @throws Exception
*/
private void initMappings() throws Exception {
@@ -317,7 +381,7 @@ private void initMappings() throws Exception {
for (Iterator iterator = mappings.iterator(); iterator
.hasNext();) {
String type = iterator.next();
- pushMapping(index, type, forceReinit, mergeMapping);
+ pushMapping(index, type, forceMapping, mergeMapping);
}
}
}
@@ -369,6 +433,59 @@ private void createAlias(String alias, String index) throws Exception {
if (!response.acknowledged()) throw new Exception("Could not define alias [" + alias + "] for index [" + index + "].");
if (logger.isTraceEnabled()) logger.trace("/createAlias("+alias+","+index+")");
}
+
+ /**
+ * Create a template if needed
+ *
+ * @param template template name
+ * @param force force recreate template
+ * @throws Exception
+ */
+ private void createTemplate(String template, boolean force)
+ throws Exception {
+ if (logger.isTraceEnabled())
+ logger.trace("createTemplate(" + template + ")");
+ checkClient();
+
+ // If template already exists and if we are in force mode, we delete the
+ // type and its mapping
+ if (force && isTemplateExist(template)) {
+ if (logger.isDebugEnabled())
+ logger.debug("Force remove template [" + template + "]");
+ // Remove template in ElasticSearch !
+ final DeleteIndexTemplateResponse response = client.admin()
+ .indices().prepareDeleteTemplate(template).execute()
+ .actionGet();
+ }
+
+ // Read the template json file if exists and use it
+ String source = readTemplate(template);
+ if (source != null) {
+ if (logger.isTraceEnabled())
+ logger.trace("Template [" + template + "]=" + source);
+ // Create template
+ final PutIndexTemplateResponse response = client.admin().indices()
+ .preparePutTemplate(template).setSource(source).execute()
+ .actionGet();
+ if (!response.acknowledged()) {
+ throw new Exception("Could not define template [" + template
+ + "].");
+ } else {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Template [" + template
+ + "] successfully created.");
+ }
+ }
+ } else {
+ if (logger.isWarnEnabled()) {
+ logger.warn("No template definition for [" + template
+ + "]. Ignoring.");
+ }
+ }
+
+ if (logger.isTraceEnabled())
+ logger.trace("/createTemplate(" + template + ")");
+ }
/**
* Check if an index already exists
@@ -395,6 +512,23 @@ private boolean isMappingExist(String index, String type) {
if (mdd != null) return true;
return false;
}
+
+ /**
+ * Check if a template already exists
+ *
+ * @param template template name
+ * @return true if template exists
+ */
+ private boolean isTemplateExist(String template) {
+ ClusterState cs = client.admin().cluster().prepareState()
+ .setFilterIndexTemplates(template).execute().actionGet()
+ .getState();
+ final IndexTemplateMetaData mdd = cs.getMetaData().templates()
+ .get(template);
+
+ if (mdd != null) return true;
+ return false;
+ }
/**
* Define a type for a given index and if exists with its mapping definition
@@ -486,9 +620,8 @@ private void createIndex(String index) throws Exception {
}
/**
- * Create a new index in Elasticsearch
+ * Create a new index in ElasticSearch
* @param index Index name
- * @param merge Try to merge settings ?
* @throws Exception
*/
private void mergeIndexSettings(String index) throws Exception {
@@ -532,6 +665,18 @@ private String readMapping(String index, String type) throws Exception {
return readFileInClasspath(classpathRoot + "/" + index + "/" + type + jsonFileExtension);
}
+ /**
+ * Read the template.
+ * Shortcut to readFileInClasspath(classpathRoot + "/_template/" + template + jsonFileExtension);
+ *
+ * @param template Template name
+ * @return Template if exists. Null otherwise.
+ * @throws Exception
+ */
+ private String readTemplate(String template) throws Exception {
+ return readFileInClasspath(classpathRoot + "/_template/" + template + jsonFileExtension);
+ }
+
/**
* Read settings for an index.
* Shortcut to readFileInClasspath(classpathRoot + "/" + index + "/" + indexSettingsFileName);
diff --git a/src/test/java/fr/pilato/spring/elasticsearch/xml/ElasticsearchTemplateTest.java b/src/test/java/fr/pilato/spring/elasticsearch/xml/ElasticsearchTemplateTest.java
new file mode 100644
index 00000000..890c9085
--- /dev/null
+++ b/src/test/java/fr/pilato/spring/elasticsearch/xml/ElasticsearchTemplateTest.java
@@ -0,0 +1,34 @@
+package fr.pilato.spring.elasticsearch.xml;
+
+import org.elasticsearch.client.Client;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+
+public class ElasticsearchTemplateTest {
+
+ static protected ConfigurableApplicationContext ctx;
+
+ @BeforeClass
+ static public void setup() {
+ ctx = new ClassPathXmlApplicationContext(
+ "fr/pilato/spring/elasticsearch/xml/es-template-test-context.xml");
+ }
+
+ @AfterClass
+ static public void tearDown() {
+ if (ctx != null) {
+ ctx.close();
+ }
+ }
+
+ @Test
+ public void test_transport_client() {
+ Client client = ctx.getBean("esClient", Client.class);
+ Assert.assertNotNull("Client must not be null...", client);
+ }
+
+}
diff --git a/src/test/resources/es-context.xml b/src/test/resources/es-context.xml
index da6eaeaf..dc14927f 100644
--- a/src/test/resources/es-context.xml
+++ b/src/test/resources/es-context.xml
@@ -26,7 +26,7 @@ http://www.springframework.org/schema/context http://www.springframework.org/sch
alltheworld:rss
-
+
\ No newline at end of file
diff --git a/src/test/resources/es6/_template/twitter_template.json b/src/test/resources/es6/_template/twitter_template.json
new file mode 100644
index 00000000..01552210
--- /dev/null
+++ b/src/test/resources/es6/_template/twitter_template.json
@@ -0,0 +1,16 @@
+{
+ "template" : "twee*",
+ "settings" : {
+ "number_of_shards" : 1
+ },
+ "mappings" : {
+ "tweet" : {
+ "properties" : {
+ "message" : {
+ "type" : "string",
+ "store" : "yes"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/fr/pilato/spring/elasticsearch/xml/es-mapping-failed-test-context.xml b/src/test/resources/fr/pilato/spring/elasticsearch/xml/es-mapping-failed-test-context.xml
index 147cf625..2e071a3f 100644
--- a/src/test/resources/fr/pilato/spring/elasticsearch/xml/es-mapping-failed-test-context.xml
+++ b/src/test/resources/fr/pilato/spring/elasticsearch/xml/es-mapping-failed-test-context.xml
@@ -20,7 +20,7 @@
twitter/tweet
-
+
twitter/tweet
-
+
twitter/tweet
-
+
twitter/tweet
-
+
+
+
+
+
+
+
+
+
+
+ twitter_template
+
+
+
+
+
+
\ No newline at end of file