diff --git a/.idea/dictionaries/kimchy.xml b/.idea/dictionaries/kimchy.xml index 902383f0bd3..203fcd182f1 100644 --- a/.idea/dictionaries/kimchy.xml +++ b/.idea/dictionaries/kimchy.xml @@ -30,6 +30,7 @@ jgroups joda jsonp + kimchy lifecycle linefeeds lucene diff --git a/.idea/modules.xml b/.idea/modules.xml index eca5d07735d..7ca869a1a29 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -6,6 +6,7 @@ + diff --git a/.idea/modules/plugin-groovy.iml b/.idea/modules/plugin-groovy.iml new file mode 100644 index 00000000000..283d48f8f07 --- /dev/null +++ b/.idea/modules/plugin-groovy.iml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/build.gradle b/build.gradle index ec62631862e..f47c06040ca 100644 --- a/build.gradle +++ b/build.gradle @@ -18,6 +18,12 @@ explodedDistLibDir = new File(explodedDistDir, 'lib') explodedDistBinDir = new File(explodedDistDir, 'bin') explodedDistConfigDir = new File(explodedDistDir, 'config') +//mavenRepoUrl = "file://localhost/" + projectDir.absolutePath + "/build/maven/repository" +//mavenSnapshotRepoUrl = "file://localhost/" + projectDir.absolutePath + "/build/maven/snapshotRepository" +mavenRepoUrl = "http://oss.sonatype.org/service/local/staging/deploy/maven2/" +mavenSnapshotRepoUrl = "http://oss.sonatype.org/content/repositories/snapshots" +mavenRepoUser = "kimchy" +mavenRepoPass = System.getenv("REPO_PASS") allprojects { group = 'org.elasticsearch' diff --git a/modules/elasticsearch/build.gradle b/modules/elasticsearch/build.gradle index cc63438d14a..af17d44f230 100644 --- a/modules/elasticsearch/build.gradle +++ b/modules/elasticsearch/build.gradle @@ -97,15 +97,12 @@ artifacts { uploadArchives { repositories.mavenDeployer { -// repository(url: "file://localhost/" + rootProject.projectDir.absolutePath + "/build/maven/repository") -// snapshotRepository(url: "file://localhost/" + rootProject.projectDir.absolutePath + "/build/maven/snapshotRepository") - configuration = configurations.deployerJars - repository(url: "http://oss.sonatype.org/service/local/staging/deploy/maven2/") { - authentication(userName: "kimchy", password: System.getenv("REPO_PASS")) + repository(url: rootProject.mavenRepoUrl) { + authentication(userName: rootProject.mavenRepoUser, password: rootProject.mavenRepoPass) } - snapshotRepository(url: "http://oss.sonatype.org/content/repositories/snapshots") { - authentication(userName: "kimchy", password: System.getenv("REPO_PASS")) + snapshotRepository(url: rootProject.mavenSnapshotRepoUrl) { + authentication(userName: rootProject.mavenRepoUser, password: rootProject.mavenRepoPass) } pom.project { diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/ActionListener.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/ActionListener.java index 6e5e3d23834..b594480c590 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/ActionListener.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/ActionListener.java @@ -20,11 +20,19 @@ package org.elasticsearch.action; /** - * @author kimchy (Shay Banon) + * A listener for action responses or failures. + * + * @author kimchy (shay.banon) */ public interface ActionListener { + /** + * A response handler. + */ void onResponse(Response response); + /** + * A failure handler. + */ void onFailure(Throwable e); } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/ActionRequest.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/ActionRequest.java index 4fe1d54afcb..920f48ea7ed 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/ActionRequest.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/ActionRequest.java @@ -22,7 +22,7 @@ package org.elasticsearch.action; import org.elasticsearch.util.io.stream.Streamable; /** - * @author kimchy (Shay Banon) + * @author kimchy (shay.banon) */ public interface ActionRequest extends Streamable { diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/ActionResponse.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/ActionResponse.java index 00c0d3a04f3..fed7847a597 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/ActionResponse.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/ActionResponse.java @@ -22,7 +22,7 @@ package org.elasticsearch.action; import org.elasticsearch.util.io.stream.Streamable; /** - * @author kimchy (Shay Banon) + * @author kimchy (shay.banon) */ public interface ActionResponse extends Streamable { } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/ListenableActionFuture.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/ListenableActionFuture.java new file mode 100644 index 00000000000..9aef24ca6ad --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/ListenableActionFuture.java @@ -0,0 +1,38 @@ +/* + * 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.action; + +/** + * An {@link ActionFuture} that listeners can be added to. + * + * @author kimchy (shay.banon) + */ +public interface ListenableActionFuture extends ActionFuture { + + /** + * Add an action listener to be invoked when a response has received. + */ + void addListener(final ActionListener listener); + + /** + * Add an action listener (runnable) to be invoked when a response has received. + */ + void addListener(final Runnable listener); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/delete/DeleteResponse.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/delete/DeleteResponse.java index 63fffa7ba50..1ceaa05980a 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/delete/DeleteResponse.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/delete/DeleteResponse.java @@ -58,6 +58,13 @@ public class DeleteResponse implements ActionResponse, Streamable { return this.index; } + /** + * The index the document was deleted from. + */ + public String getIndex() { + return index; + } + /** * The type of the document deleted. */ @@ -65,6 +72,13 @@ public class DeleteResponse implements ActionResponse, Streamable { return this.type; } + /** + * The type of the document deleted. + */ + public String getType() { + return type; + } + /** * The id of the document deleted. */ @@ -72,6 +86,13 @@ public class DeleteResponse implements ActionResponse, Streamable { return this.id; } + /** + * The id of the document deleted. + */ + public String getId() { + return id; + } + @Override public void readFrom(StreamInput in) throws IOException { index = in.readUTF(); id = in.readUTF(); diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/get/GetField.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/get/GetField.java index e96332effd7..293a4caeae7 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/get/GetField.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/get/GetField.java @@ -50,10 +50,18 @@ public class GetField implements Streamable, Iterable { return name; } + public String getName() { + return name; + } + public List values() { return values; } + public List getValues() { + return values; + } + @Override public Iterator iterator() { return values.iterator(); } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/get/GetResponse.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/get/GetResponse.java index b74997123c0..678eae81a05 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/get/GetResponse.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/get/GetResponse.java @@ -55,6 +55,8 @@ public class GetResponse implements ActionResponse, Streamable, Iterable fields; + private Map sourceAsMap; + private byte[] source; GetResponse() { @@ -79,6 +81,13 @@ public class GetResponse implements ActionResponse, Streamable, Iterable getSource() { + return sourceAsMap(); + } + public Map fields() { return this.fields; } + public Map getFields() { + return fields; + } + public GetField field(String name) { return fields.get(name); } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/index/IndexRequest.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/index/IndexRequest.java index 710b34363b0..c2e3e259e8c 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/index/IndexRequest.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/index/IndexRequest.java @@ -104,6 +104,9 @@ public class IndexRequest extends ShardReplicationOperationRequest { private byte[] source; private OpType opType = OpType.INDEX; + public IndexRequest() { + } + /** * Constructs a new index request against the specific index. The {@link #type(String)}, * {@link #id(String)} and {@link #source(byte[])} must be set. @@ -127,9 +130,6 @@ public class IndexRequest extends ShardReplicationOperationRequest { this.source = source; } - IndexRequest() { - } - @Override public ActionRequestValidationException validate() { ActionRequestValidationException validationException = super.validate(); if (type == null) { diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/index/IndexResponse.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/index/IndexResponse.java index a7528048246..71e82f78e09 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/index/IndexResponse.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/index/IndexResponse.java @@ -58,6 +58,13 @@ public class IndexResponse implements ActionResponse, Streamable { return this.index; } + /** + * The index the document was indexed into. + */ + public String getIndex() { + return index; + } + /** * The type of the document indexed. */ @@ -65,6 +72,13 @@ public class IndexResponse implements ActionResponse, Streamable { return this.type; } + /** + * The type of the document indexed. + */ + public String getType() { + return type; + } + /** * The id of the document indexed. */ @@ -72,6 +86,13 @@ public class IndexResponse implements ActionResponse, Streamable { return this.id; } + /** + * The id of the document indexed. + */ + public String getId() { + return id; + } + @Override public void readFrom(StreamInput in) throws IOException { index = in.readUTF(); id = in.readUTF(); diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/AbstractListenableActionFuture.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/AbstractListenableActionFuture.java new file mode 100644 index 00000000000..8884ebe5c02 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/AbstractListenableActionFuture.java @@ -0,0 +1,128 @@ +/* + * 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.action.support; + +import org.elasticsearch.ElasticSearchException; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.threadpool.ThreadPool; + +import java.util.List; + +import static com.google.common.collect.Lists.*; + +/** + * @author kimchy (shay.banon) + */ +public abstract class AbstractListenableActionFuture extends AdapterActionFuture { + + private final boolean listenerThreaded; + + private final ThreadPool threadPool; + + private volatile Object listeners; + + private boolean executedListeners = false; + + protected AbstractListenableActionFuture(boolean listenerThreaded, ThreadPool threadPool) { + this.listenerThreaded = listenerThreaded; + this.threadPool = threadPool; + } + + public void addListener(final ActionListener listener) { + internalAddListener(listener); + } + + public void addListener(final Runnable listener) { + internalAddListener(listener); + } + + public void internalAddListener(Object listener) { + boolean executeImmediate = false; + synchronized (this) { + if (executedListeners) { + executeImmediate = true; + } else { + Object listeners = this.listeners; + if (listeners == null) { + listeners = listener; + } else if (listeners instanceof List) { + ((List) this.listeners).add(listener); + } else { + Object orig = listeners; + listeners = newArrayListWithExpectedSize(2); + ((List) listeners).add(orig); + ((List) listeners).add(listener); + } + this.listeners = listeners; + } + } + if (executeImmediate) { + executeListener(listener); + } + } + + @Override protected void done() { + super.done(); + synchronized (this) { + executedListeners = true; + } + Object listeners = this.listeners; + if (listeners != null) { + if (listeners instanceof List) { + List list = (List) listeners; + for (Object listener : list) { + executeListener(listener); + } + } else { + executeListener(listeners); + } + } + } + + private void executeListener(final Object listener) { + if (listenerThreaded) { + if (listener instanceof Runnable) { + threadPool.execute((Runnable) listener); + } else { + threadPool.execute(new Runnable() { + @Override public void run() { + ActionListener lst = (ActionListener) listener; + try { + lst.onResponse(actionGet()); + } catch (ElasticSearchException e) { + lst.onFailure(e); + } + } + }); + } + } else { + if (listener instanceof Runnable) { + ((Runnable) listener).run(); + } else { + ActionListener lst = (ActionListener) listener; + try { + lst.onResponse(actionGet()); + } catch (ElasticSearchException e) { + lst.onFailure(e); + } + } + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/AdapterActionFuture.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/AdapterActionFuture.java new file mode 100644 index 00000000000..4407909e717 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/AdapterActionFuture.java @@ -0,0 +1,91 @@ +/* + * 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.action.support; + +import org.elasticsearch.ElasticSearchException; +import org.elasticsearch.ElasticSearchInterruptedException; +import org.elasticsearch.ElasticSearchTimeoutException; +import org.elasticsearch.action.ActionFuture; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.transport.TransportException; +import org.elasticsearch.util.TimeValue; +import org.elasticsearch.util.concurrent.AbstractFuture; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * @author kimchy (shay.banon) + */ +public abstract class AdapterActionFuture extends AbstractFuture implements ActionFuture, ActionListener { + + @Override public T actionGet() throws ElasticSearchException { + try { + return get(); + } catch (InterruptedException e) { + throw new ElasticSearchInterruptedException(e.getMessage()); + } catch (ExecutionException e) { + if (e.getCause() instanceof ElasticSearchException) { + throw (ElasticSearchException) e.getCause(); + } else { + throw new TransportException("Failed execution", e); + } + } + } + + @Override public T actionGet(String timeout) throws ElasticSearchException { + return actionGet(TimeValue.parseTimeValue(timeout, null)); + } + + @Override public T actionGet(long timeoutMillis) throws ElasticSearchException { + return actionGet(timeoutMillis, TimeUnit.MILLISECONDS); + } + + @Override public T actionGet(TimeValue timeout) throws ElasticSearchException { + return actionGet(timeout.millis(), TimeUnit.MILLISECONDS); + } + + @Override public T actionGet(long timeout, TimeUnit unit) throws ElasticSearchException { + try { + return get(timeout, unit); + } catch (TimeoutException e) { + throw new ElasticSearchTimeoutException(e.getMessage()); + } catch (InterruptedException e) { + throw new ElasticSearchInterruptedException(e.getMessage()); + } catch (ExecutionException e) { + if (e.getCause() instanceof ElasticSearchException) { + throw (ElasticSearchException) e.getCause(); + } else { + throw new ElasticSearchException("Failed execution", e); + } + } + } + + @Override public void onResponse(L result) { + set(convert(result)); + } + + @Override public void onFailure(Throwable e) { + setException(e); + } + + protected abstract T convert(L listenerResponse); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/PlainActionFuture.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/PlainActionFuture.java index 4d502a39864..dc026ba23e8 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/PlainActionFuture.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/PlainActionFuture.java @@ -19,143 +19,16 @@ package org.elasticsearch.action.support; -import org.elasticsearch.ElasticSearchException; -import org.elasticsearch.ElasticSearchInterruptedException; -import org.elasticsearch.ElasticSearchTimeoutException; -import org.elasticsearch.action.ActionFuture; -import org.elasticsearch.action.ActionListener; -import org.elasticsearch.transport.TransportException; -import org.elasticsearch.util.TimeValue; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - /** * @author kimchy (shay.banon) */ -public class PlainActionFuture implements ActionFuture, ActionListener { +public class PlainActionFuture extends AdapterActionFuture { public static PlainActionFuture newFuture() { return new PlainActionFuture(); } - private final CountDownLatch latch; - - private volatile boolean done; - private volatile boolean canceled; - private volatile T result; - private volatile Throwable exp; - - public PlainActionFuture() { - latch = new CountDownLatch(1); - } - - @Override public boolean cancel(boolean mayInterruptIfRunning) { - if (done) - return true; - - canceled = true; - latch.countDown(); - return true; - } - - @Override public boolean isCancelled() { - return canceled; - } - - @Override public boolean isDone() { - return done; - } - - @Override public T get() throws InterruptedException, ExecutionException { - latch.await(); - - if (!done || canceled) { - throw new InterruptedException("future was interrupted"); - } - - if (exp != null) { - throw new ExecutionException(exp.getMessage(), exp); - } - - return this.result; - } - - @Override public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - latch.await(timeout, unit); - - if (!done || canceled) { - throw new TimeoutException("response did not arrive"); - } - - if (exp != null) { - throw new ExecutionException(exp.getMessage(), exp); - } - - return this.result; - } - - @Override public T actionGet() throws ElasticSearchException { - try { - return get(); - } catch (InterruptedException e) { - throw new ElasticSearchInterruptedException(e.getMessage()); - } catch (ExecutionException e) { - if (e.getCause() instanceof ElasticSearchException) { - throw (ElasticSearchException) e.getCause(); - } else { - throw new TransportException("Failed execution", e); - } - } - } - - @Override public T actionGet(String timeout) throws ElasticSearchException { - return actionGet(TimeValue.parseTimeValue(timeout, null)); - } - - @Override public T actionGet(long timeoutMillis) throws ElasticSearchException { - return actionGet(timeoutMillis, TimeUnit.MILLISECONDS); - } - - @Override public T actionGet(TimeValue timeout) throws ElasticSearchException { - return actionGet(timeout.millis(), TimeUnit.MILLISECONDS); - } - - @Override public T actionGet(long timeout, TimeUnit unit) throws ElasticSearchException { - try { - return get(timeout, unit); - } catch (TimeoutException e) { - throw new ElasticSearchTimeoutException(e.getMessage()); - } catch (InterruptedException e) { - throw new ElasticSearchInterruptedException(e.getMessage()); - } catch (ExecutionException e) { - if (e.getCause() instanceof ElasticSearchException) { - throw (ElasticSearchException) e.getCause(); - } else { - throw new ElasticSearchException("Failed execution", e); - } - } - } - - @Override public void onResponse(T result) { - this.done = true; - this.result = result; - - if (canceled) - return; - - latch.countDown(); - } - - @Override public void onFailure(Throwable e) { - this.done = true; - this.exp = e; - - if (canceled) - return; - - latch.countDown(); + @Override protected T convert(T listenerResponse) { + return listenerResponse; } } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/PlainListenableActionFuture.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/PlainListenableActionFuture.java new file mode 100644 index 00000000000..a8089f15bd8 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/PlainListenableActionFuture.java @@ -0,0 +1,36 @@ +/* + * 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.action.support; + +import org.elasticsearch.threadpool.ThreadPool; + +/** + * @author kimchy (shay.banon) + */ +public class PlainListenableActionFuture extends AbstractListenableActionFuture { + + public PlainListenableActionFuture(boolean listenerThreaded, ThreadPool threadPool) { + super(listenerThreaded, threadPool); + } + + @Override protected T convert(T response) { + return response; + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/broadcast/BroadcastOperationResponse.java b/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/broadcast/BroadcastOperationResponse.java index 1bbae5975e8..301d07b7362 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/broadcast/BroadcastOperationResponse.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/action/support/broadcast/BroadcastOperationResponse.java @@ -66,6 +66,13 @@ public abstract class BroadcastOperationResponse implements ActionResponse { return totalShards; } + /** + * The total shards this request ran against. + */ + public int getTotalShards() { + return totalShards; + } + /** * The successful shards this request was executed on. */ @@ -73,6 +80,13 @@ public abstract class BroadcastOperationResponse implements ActionResponse { return successfulShards; } + /** + * The successful shards this request was executed on. + */ + public int getSuccessfulShards() { + return successfulShards; + } + /** * The failed shards this request was executed on. */ @@ -80,6 +94,13 @@ public abstract class BroadcastOperationResponse implements ActionResponse { return failedShards; } + /** + * The failed shards this request was executed on. + */ + public int getFailedShards() { + return failedShards; + } + /** * The list of shard failures exception. */ @@ -90,6 +111,13 @@ public abstract class BroadcastOperationResponse implements ActionResponse { return shardFailures; } + /** + * The list of shard failures exception. + */ + public List getShardFailures() { + return shardFailures; + } + @Override public void readFrom(StreamInput in) throws IOException { totalShards = in.readVInt(); successfulShards = in.readVInt(); diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/Requests.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/Requests.java index 0bb1cb5ab4f..0063eb806bb 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/Requests.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/Requests.java @@ -53,6 +53,10 @@ import org.elasticsearch.action.terms.TermsRequest; */ public class Requests { + public static IndexRequest indexRequest() { + return new IndexRequest(); + } + /** * Create an index request against a specific index. Note the {@link IndexRequest#type(String)} must be * set as well and optionally the {@link IndexRequest#id(String)}. diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/internal/InternalClient.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/internal/InternalClient.java new file mode 100644 index 00000000000..5506a048f20 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/internal/InternalClient.java @@ -0,0 +1,31 @@ +/* + * 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.client.internal; + +import org.elasticsearch.client.Client; +import org.elasticsearch.threadpool.ThreadPool; + +/** + * @author kimchy (shay.banon) + */ +public interface InternalClient extends Client { + + ThreadPool threadPool(); +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/node/NodeClient.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/node/NodeClient.java index 0c2025398fc..f364197438a 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/node/NodeClient.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/node/NodeClient.java @@ -44,14 +44,17 @@ import org.elasticsearch.action.terms.TermsRequest; import org.elasticsearch.action.terms.TermsResponse; import org.elasticsearch.action.terms.TransportTermsAction; import org.elasticsearch.client.AdminClient; -import org.elasticsearch.client.Client; +import org.elasticsearch.client.internal.InternalClient; +import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.util.component.AbstractComponent; import org.elasticsearch.util.settings.Settings; /** * @author kimchy (shay.banon) */ -public class NodeClient extends AbstractComponent implements Client { +public class NodeClient extends AbstractComponent implements InternalClient { + + private final ThreadPool threadPool; private final NodeAdminClient admin; @@ -73,12 +76,13 @@ public class NodeClient extends AbstractComponent implements Client { private final TransportMoreLikeThisAction moreLikeThisAction; - @Inject public NodeClient(Settings settings, NodeAdminClient admin, + @Inject public NodeClient(Settings settings, ThreadPool threadPool, NodeAdminClient admin, TransportIndexAction indexAction, TransportDeleteAction deleteAction, TransportDeleteByQueryAction deleteByQueryAction, TransportGetAction getAction, TransportCountAction countAction, TransportSearchAction searchAction, TransportSearchScrollAction searchScrollAction, TransportTermsAction termsAction, TransportMoreLikeThisAction moreLikeThisAction) { super(settings); + this.threadPool = threadPool; this.admin = admin; this.indexAction = indexAction; this.deleteAction = deleteAction; @@ -91,6 +95,10 @@ public class NodeClient extends AbstractComponent implements Client { this.moreLikeThisAction = moreLikeThisAction; } + @Override public ThreadPool threadPool() { + return this.threadPool; + } + @Override public void close() { // nothing really to do } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/TransportClient.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/TransportClient.java index 482ffab5100..50765c44376 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/TransportClient.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/TransportClient.java @@ -43,7 +43,7 @@ import org.elasticsearch.action.search.SearchScrollRequest; import org.elasticsearch.action.terms.TermsRequest; import org.elasticsearch.action.terms.TermsResponse; import org.elasticsearch.client.AdminClient; -import org.elasticsearch.client.Client; +import org.elasticsearch.client.internal.InternalClient; import org.elasticsearch.client.transport.action.ClientTransportActionModule; import org.elasticsearch.client.transport.support.InternalTransportClient; import org.elasticsearch.cluster.ClusterNameModule; @@ -77,7 +77,7 @@ import static org.elasticsearch.util.settings.ImmutableSettings.*; * * @author kimchy (shay.banon) */ -public class TransportClient implements Client { +public class TransportClient implements InternalClient { private final Injector injector; @@ -209,6 +209,10 @@ public class TransportClient implements Client { ThreadLocals.clearReferencesThreadLocals(); } + @Override public ThreadPool threadPool() { + return internalClient.threadPool(); + } + @Override public AdminClient admin() { return internalClient.admin(); } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/support/InternalTransportClient.java b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/support/InternalTransportClient.java index 9e9a7fc9052..a036ad2d467 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/support/InternalTransportClient.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/client/transport/support/InternalTransportClient.java @@ -40,7 +40,7 @@ import org.elasticsearch.action.search.SearchScrollRequest; import org.elasticsearch.action.terms.TermsRequest; import org.elasticsearch.action.terms.TermsResponse; import org.elasticsearch.client.AdminClient; -import org.elasticsearch.client.Client; +import org.elasticsearch.client.internal.InternalClient; import org.elasticsearch.client.transport.TransportClientNodesService; import org.elasticsearch.client.transport.action.count.ClientTransportCountAction; import org.elasticsearch.client.transport.action.delete.ClientTransportDeleteAction; @@ -52,13 +52,16 @@ import org.elasticsearch.client.transport.action.search.ClientTransportSearchAct import org.elasticsearch.client.transport.action.search.ClientTransportSearchScrollAction; import org.elasticsearch.client.transport.action.terms.ClientTransportTermsAction; import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.util.component.AbstractComponent; import org.elasticsearch.util.settings.Settings; /** * @author kimchy (Shay Banon) */ -public class InternalTransportClient extends AbstractComponent implements Client { +public class InternalTransportClient extends AbstractComponent implements InternalClient { + + private final ThreadPool threadPool; private final TransportClientNodesService nodesService; @@ -82,12 +85,14 @@ public class InternalTransportClient extends AbstractComponent implements Client private final ClientTransportMoreLikeThisAction moreLikeThisAction; - @Inject public InternalTransportClient(Settings settings, TransportClientNodesService nodesService, InternalTransportAdminClient adminClient, + @Inject public InternalTransportClient(Settings settings, ThreadPool threadPool, + TransportClientNodesService nodesService, InternalTransportAdminClient adminClient, ClientTransportIndexAction indexAction, ClientTransportDeleteAction deleteAction, ClientTransportGetAction getAction, ClientTransportDeleteByQueryAction deleteByQueryAction, ClientTransportCountAction countAction, ClientTransportSearchAction searchAction, ClientTransportSearchScrollAction searchScrollAction, ClientTransportTermsAction termsAction, ClientTransportMoreLikeThisAction moreLikeThisAction) { super(settings); + this.threadPool = threadPool; this.nodesService = nodesService; this.adminClient = adminClient; @@ -106,6 +111,10 @@ public class InternalTransportClient extends AbstractComponent implements Client // nothing to do here } + @Override public ThreadPool threadPool() { + return this.threadPool; + } + @Override public AdminClient admin() { return adminClient; } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/transport/PlainTransportFuture.java b/modules/elasticsearch/src/main/java/org/elasticsearch/transport/PlainTransportFuture.java index 93ebb73d17f..3f277f10b7a 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/transport/PlainTransportFuture.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/transport/PlainTransportFuture.java @@ -21,73 +21,22 @@ package org.elasticsearch.transport; import org.elasticsearch.ElasticSearchException; import org.elasticsearch.ElasticSearchInterruptedException; +import org.elasticsearch.util.concurrent.AbstractFuture; import org.elasticsearch.util.io.stream.Streamable; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** - * @author kimchy (Shay Banon) + * @author kimchy (shay.banon) */ -public class PlainTransportFuture implements TransportFuture, TransportResponseHandler { +public class PlainTransportFuture extends AbstractFuture implements TransportFuture, TransportResponseHandler { - private final CountDownLatch latch; private final TransportResponseHandler handler; - private volatile boolean done; - private volatile boolean canceled; - private volatile V result; - private volatile Exception exp; public PlainTransportFuture(TransportResponseHandler handler) { this.handler = handler; - latch = new CountDownLatch(1); - } - - @Override public boolean cancel(boolean mayInterruptIfRunning) { - if (done) - return true; - - canceled = true; - latch.countDown(); - return true; - } - - @Override public boolean isCancelled() { - return canceled; - } - - @Override public boolean isDone() { - return done; - } - - @Override public V get() throws InterruptedException, ExecutionException { - latch.await(); - - if (!done || canceled) { - throw new InterruptedException("future was interrupted"); - } - - if (exp != null) { - throw new ExecutionException(exp.getMessage(), exp); - } - - return this.result; - } - - @Override public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - latch.await(timeout, unit); - - if (!done || canceled) { - throw new TimeoutException("response did not arrive"); - } - - if (exp != null) { - throw new ExecutionException(exp.getMessage(), exp); - } - - return this.result; } @Override public V txGet() throws ElasticSearchException { @@ -123,25 +72,13 @@ public class PlainTransportFuture implements TransportFutu } @Override public void handleResponse(V response) { - this.done = true; - this.result = response; - - if (canceled) - return; - handler.handleResponse(response); - latch.countDown(); + set(response); } @Override public void handleException(RemoteTransportException exp) { - this.done = true; - this.exp = exp; - - if (canceled) - return; - handler.handleException(exp); - latch.countDown(); + setException(exp); } @Override public boolean spawn() { diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/concurrent/AbstractFuture.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/concurrent/AbstractFuture.java new file mode 100644 index 00000000000..ebc15d21d22 --- /dev/null +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/concurrent/AbstractFuture.java @@ -0,0 +1,328 @@ +/* + * 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.util.concurrent; + +import java.util.concurrent.*; +import java.util.concurrent.locks.AbstractQueuedSynchronizer; + +/** + *

An abstract implementation of the {@link Future} interface. This class + * is an abstraction of {@link java.util.concurrent.FutureTask} to support use + * for tasks other than {@link Runnable}s. It uses an + * {@link AbstractQueuedSynchronizer} to deal with concurrency issues and + * guarantee thread safety. It could be used as a base class to + * {@code FutureTask}, or any other implementor of the {@code Future} interface. + * + *

This class implements all methods in {@code Future}. Subclasses should + * provide a way to set the result of the computation through the protected + * methods {@link #set(Object)}, {@link #setException(Throwable)}, or + * {@link #cancel()}. If subclasses want to implement cancellation they can + * override the {@link #cancel(boolean)} method with a real implementation, the + * default implementation doesn't support cancellation. + * + *

The state changing methods all return a boolean indicating success or + * failure in changing the future's state. Valid states are running, + * completed, failed, or cancelled. Because this class does not implement + * cancellation it is left to the subclass to distinguish between created + * and running tasks. + */ +public abstract class AbstractFuture implements Future { + + /** + * Synchronization control for AbstractFutures. + */ + private final Sync sync = new Sync(); + + /* + * Blocks until either the task completes or the timeout expires. Uses the + * sync blocking-with-timeout support provided by AQS. + */ + + public V get(long timeout, TimeUnit unit) throws InterruptedException, + TimeoutException, ExecutionException { + return sync.get(unit.toNanos(timeout)); + } + + /* + * Blocks until the task completes or we get interrupted. Uses the + * interruptible blocking support provided by AQS. + */ + + public V get() throws InterruptedException, ExecutionException { + return sync.get(); + } + + /* + * Checks if the sync is not in the running state. + */ + + public boolean isDone() { + return sync.isDone(); + } + + /* + * Checks if the sync is in the cancelled state. + */ + + public boolean isCancelled() { + return sync.isCancelled(); + } + + /* + * Default implementation of cancel that never cancels the future. + * Subclasses should override this to implement cancellation if desired. + */ + + public boolean cancel(boolean mayInterruptIfRunning) { + return false; + } + + /** + * Subclasses should invoke this method to set the result of the computation + * to {@code value}. This will set the state of the future to + * {@link AbstractFuture.Sync#COMPLETED} and call {@link #done()} if the + * state was successfully changed. + * + * @param value the value that was the result of the task. + * @return true if the state was successfully changed. + */ + protected boolean set(V value) { + boolean result = sync.set(value); + if (result) { + done(); + } + return result; + } + + /** + * Subclasses should invoke this method to set the result of the computation + * to an error, {@code throwable}. This will set the state of the future to + * {@link AbstractFuture.Sync#COMPLETED} and call {@link #done()} if the + * state was successfully changed. + * + * @param throwable the exception that the task failed with. + * @return true if the state was successfully changed. + * @throws Error if the throwable was an {@link Error}. + */ + protected boolean setException(Throwable throwable) { + boolean result = sync.setException(throwable); + if (result) { + done(); + } + + // If it's an Error, we want to make sure it reaches the top of the + // call stack, so we rethrow it. + if (throwable instanceof Error) { + throw (Error) throwable; + } + return result; + } + + /** + * Subclasses should invoke this method to mark the future as cancelled. + * This will set the state of the future to {@link + * AbstractFuture.Sync#CANCELLED} and call {@link #done()} if the state was + * successfully changed. + * + * @return true if the state was successfully changed. + */ + protected final boolean cancel() { + boolean result = sync.cancel(); + if (result) { + done(); + } + return result; + } + + /* + * Called by the success, failed, or cancelled methods to indicate that the + * value is now available and the latch can be released. Subclasses can + * use this method to deal with any actions that should be undertaken when + * the task has completed. + */ + + protected void done() { + // Default implementation does nothing. + } + + /** + *

Following the contract of {@link AbstractQueuedSynchronizer} we create a + * private subclass to hold the synchronizer. This synchronizer is used to + * implement the blocking and waiting calls as well as to handle state changes + * in a thread-safe manner. The current state of the future is held in the + * Sync state, and the lock is released whenever the state changes to either + * {@link #COMPLETED} or {@link #CANCELLED}. + * + *

To avoid races between threads doing release and acquire, we transition + * to the final state in two steps. One thread will successfully CAS from + * RUNNING to COMPLETING, that thread will then set the result of the + * computation, and only then transition to COMPLETED or CANCELLED. + * + *

We don't use the integer argument passed between acquire methods so we + * pass around a -1 everywhere. + */ + static final class Sync extends AbstractQueuedSynchronizer { + + private static final long serialVersionUID = 0L; + + /* Valid states. */ + static final int RUNNING = 0; + static final int COMPLETING = 1; + static final int COMPLETED = 2; + static final int CANCELLED = 4; + + private V value; + private ExecutionException exception; + + /* + * Acquisition succeeds if the future is done, otherwise it fails. + */ + + @Override + protected int tryAcquireShared(int ignored) { + if (isDone()) { + return 1; + } + return -1; + } + + /* + * We always allow a release to go through, this means the state has been + * successfully changed and the result is available. + */ + + @Override + protected boolean tryReleaseShared(int finalState) { + setState(finalState); + return true; + } + + /** + * Blocks until the task is complete or the timeout expires. Throws a + * {@link TimeoutException} if the timer expires, otherwise behaves like + * {@link #get()}. + */ + V get(long nanos) throws TimeoutException, CancellationException, + ExecutionException, InterruptedException { + + // Attempt to acquire the shared lock with a timeout. + if (!tryAcquireSharedNanos(-1, nanos)) { + throw new TimeoutException("Timeout waiting for task."); + } + + return getValue(); + } + + /** + * Blocks until {@link #complete(Object, Throwable, int)} has been + * successfully called. Throws a {@link CancellationException} if the task + * was cancelled, or a {@link ExecutionException} if the task completed with + * an error. + */ + V get() throws CancellationException, ExecutionException, + InterruptedException { + + // Acquire the shared lock allowing interruption. + acquireSharedInterruptibly(-1); + return getValue(); + } + + /** + * Implementation of the actual value retrieval. Will return the value + * on success, an exception on failure, a cancellation on cancellation, or + * an illegal state if the synchronizer is in an invalid state. + */ + private V getValue() throws CancellationException, ExecutionException { + int state = getState(); + switch (state) { + case COMPLETED: + if (exception != null) { + throw exception; + } else { + return value; + } + + case CANCELLED: + throw new CancellationException("Task was cancelled."); + + default: + throw new IllegalStateException( + "Error, synchronizer in invalid state: " + state); + } + } + + /** + * Checks if the state is {@link #COMPLETED} or {@link #CANCELLED}. + */ + boolean isDone() { + return (getState() & (COMPLETED | CANCELLED)) != 0; + } + + /** + * Checks if the state is {@link #CANCELLED}. + */ + boolean isCancelled() { + return getState() == CANCELLED; + } + + /** + * Transition to the COMPLETED state and set the value. + */ + boolean set(V v) { + return complete(v, null, COMPLETED); + } + + /** + * Transition to the COMPLETED state and set the exception. + */ + boolean setException(Throwable t) { + return complete(null, t, COMPLETED); + } + + /** + * Transition to the CANCELLED state. + */ + boolean cancel() { + return complete(null, null, CANCELLED); + } + + /** + * Implementation of completing a task. Either {@code v} or {@code t} will + * be set but not both. The {@code finalState} is the state to change to + * from {@link #RUNNING}. If the state is not in the RUNNING state we + * return {@code false}. + * + * @param v the value to set as the result of the computation. + * @param t the exception to set as the result of the computation. + * @param finalState the state to transition to. + */ + private boolean complete(V v, Throwable t, int finalState) { + if (compareAndSetState(RUNNING, COMPLETING)) { + this.value = v; + this.exception = t == null ? null : new ExecutionException(t); + releaseShared(finalState); + return true; + } + + // The state was not RUNNING, so there are no valid transitions. + return false; + } + } +} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/json/BinaryJsonBuilder.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/json/BinaryJsonBuilder.java index 3c863006eb1..b72935ea2e2 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/util/json/BinaryJsonBuilder.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/json/BinaryJsonBuilder.java @@ -65,8 +65,6 @@ public class BinaryJsonBuilder extends JsonBuilder { private final JsonFactory factory; - private StringBuilder cachedStringBuilder; - public BinaryJsonBuilder() throws IOException { this(Jackson.defaultJsonFactory()); } @@ -85,10 +83,6 @@ public class BinaryJsonBuilder extends JsonBuilder { this.builder = this; } - @Override protected StringBuilder cachedStringBuilder() { - return cachedStringBuilder; - } - @Override public BinaryJsonBuilder raw(byte[] json) throws IOException { flush(); bos.write(json); diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/json/JsonBuilder.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/json/JsonBuilder.java index d3e4c7649df..8b447b379d1 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/util/json/JsonBuilder.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/json/JsonBuilder.java @@ -69,6 +69,8 @@ public abstract class JsonBuilder { protected FieldCaseConversion fieldCaseConversion = globalFieldCaseConversion; + protected StringBuilder cachedStringBuilder; + public static StringJsonBuilder stringJsonBuilder() throws IOException { return StringJsonBuilder.Cached.cached(); } @@ -143,9 +145,9 @@ public abstract class JsonBuilder { public T field(String name) throws IOException { if (fieldCaseConversion == FieldCaseConversion.UNDERSCORE) { - name = Strings.toUnderscoreCase(name); + name = Strings.toUnderscoreCase(name, cachedStringBuilder); } else if (fieldCaseConversion == FieldCaseConversion.CAMELCASE) { - name = Strings.toCamelCase(name); + name = Strings.toCamelCase(name, cachedStringBuilder); } generator.writeFieldName(name); return builder; @@ -403,10 +405,6 @@ public abstract class JsonBuilder { public abstract String string() throws IOException; - protected StringBuilder cachedStringBuilder() { - return null; - } - public void close() { try { generator.close(); diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/util/json/StringJsonBuilder.java b/modules/elasticsearch/src/main/java/org/elasticsearch/util/json/StringJsonBuilder.java index bf7e1f78188..c96520cf9fc 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/util/json/StringJsonBuilder.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/util/json/StringJsonBuilder.java @@ -69,8 +69,6 @@ public class StringJsonBuilder extends JsonBuilder { final UnicodeUtil.UTF8Result utf8Result = new UnicodeUtil.UTF8Result(); - private StringBuilder cachedStringBuilder; - public StringJsonBuilder() throws IOException { this(Jackson.defaultJsonFactory()); } @@ -89,10 +87,6 @@ public class StringJsonBuilder extends JsonBuilder { this.builder = this; } - @Override protected StringBuilder cachedStringBuilder() { - return cachedStringBuilder; - } - @Override public StringJsonBuilder raw(byte[] json) throws IOException { flush(); Unicode.UTF16Result result = Unicode.unsafeFromBytesAsUtf16(json); diff --git a/plugins/attachments/build.gradle b/plugins/attachments/build.gradle index a4fbcc2567f..4d878e084fc 100644 --- a/plugins/attachments/build.gradle +++ b/plugins/attachments/build.gradle @@ -107,15 +107,12 @@ artifacts { uploadArchives { repositories.mavenDeployer { -// repository(url: "file://localhost/" + rootProject.projectDir.absolutePath + "/build/maven/repository") -// snapshotRepository(url: "file://localhost/" + rootProject.projectDir.absolutePath + "/build/maven/snapshotRepository") - configuration = configurations.deployerJars - repository(url: "http://oss.sonatype.org/service/local/staging/deploy/maven2/") { - authentication(userName: "kimchy", password: System.getenv("REPO_PASS")) + repository(url: rootProject.mavenRepoUrl) { + authentication(userName: rootProject.mavenRepoUser, password: rootProject.mavenRepoPass) } - snapshotRepository(url: "http://oss.sonatype.org/content/repositories/snapshots") { - authentication(userName: "kimchy", password: System.getenv("REPO_PASS")) + snapshotRepository(url: rootProject.mavenSnapshotRepoUrl) { + authentication(userName: rootProject.mavenRepoUser, password: rootProject.mavenRepoPass) } pom.project { diff --git a/plugins/groovy/build.gradle b/plugins/groovy/build.gradle new file mode 100644 index 00000000000..ed477725287 --- /dev/null +++ b/plugins/groovy/build.gradle @@ -0,0 +1,138 @@ +dependsOn(':elasticsearch') + +apply plugin: 'groovy' +apply plugin: 'maven' + +archivesBaseName = "elasticsearch-groovy" + +explodedDistDir = new File(distsDir, 'exploded') + +manifest.mainAttributes("Implementation-Title": "ElasticSearch::Plugins::Groovy", "Implementation-Version": rootProject.version, "Implementation-Date": buildTimeStr) + +configurations.compile.transitive = true +configurations.testCompile.transitive = true + +// no need to use the resource dir +sourceSets.main.resources.srcDir 'src/main/groovy' +sourceSets.test.resources.srcDir 'src/test/groovy' + +// add the source files to the dist jar +jar { + from sourceSets.main.allSource +} + +configurations { + dists + distLib { + visible = false + transitive = false + } +} + +dependencies { + compile project(':elasticsearch') + + groovy group: 'org.codehaus.groovy', name: 'groovy', version: '1.7.2' + + testCompile project(':test-testng') + testCompile('org.testng:testng:5.10:jdk15') { transitive = false } + testCompile 'org.hamcrest:hamcrest-all:1.1' +} + +test { + useTestNG() + jmvArgs = ["-ea", "-Xmx1024m"] + suiteName = project.name + listeners = ["org.elasticsearch.util.testng.Listeners"] + systemProperties["es.test.log.conf"] = System.getProperty("es.test.log.conf", "log4j-gradle.properties") +} + +task explodedDist(dependsOn: [jar], description: 'Builds the plugin zip file') << { + [explodedDistDir]*.mkdirs() + + copy { + from configurations.distLib + into explodedDistDir + } + + // remove elasticsearch files (compile above adds the elasticsearch one) + ant.delete { fileset(dir: explodedDistDir, includes: "elasticsearch-*.jar") } + + copy { + from libsDir + into explodedDistDir + } + + ant.delete { fileset(dir: explodedDistDir, includes: "elasticsearch-*-javadoc.jar") } + ant.delete { fileset(dir: explodedDistDir, includes: "elasticsearch-*-sources.jar") } +} + +task zip(type: Zip, dependsOn: ['explodedDist']) { + from(explodedDistDir) { + } +} + +task release(dependsOn: [zip]) << { + ant.delete(dir: explodedDistDir) + copy { + from distsDir + into(new File(rootProject.distsDir, "plugins")) + } +} + +configurations { + deployerJars +} + +dependencies { + deployerJars "org.apache.maven.wagon:wagon-http:1.0-beta-2" +} + +task sourcesJar(type: Jar, dependsOn: classes) { + classifier = 'sources' + from sourceSets.main.allSource +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = 'javadoc' + from javadoc.destinationDir +} + +artifacts { + archives sourcesJar + archives javadocJar +} + +uploadArchives { + repositories.mavenDeployer { + configuration = configurations.deployerJars + repository(url: rootProject.mavenRepoUrl) { + authentication(userName: rootProject.mavenRepoUser, password: rootProject.mavenRepoPass) + } + snapshotRepository(url: rootProject.mavenSnapshotRepoUrl) { + authentication(userName: rootProject.mavenRepoUser, password: rootProject.mavenRepoPass) + } + + pom.project { + inceptionYear '2009' + name 'elasticsearch-plugins-groovy' + description 'Groovy Plugin for ElasticSearch' + licenses { + license { + name 'The Apache Software License, Version 2.0' + url 'http://www.apache.org/licenses/LICENSE-2.0.txt' + distribution 'repo' + } + } + scm { + connection 'git://github.com/elasticsearch/elasticsearch.git' + developerConnection 'git@github.com:elasticsearch/elasticsearch.git' + url 'http://github.com/elasticsearch/elasticsearch' + } + } + + pom.whenConfigured {pom -> + pom.dependencies = pom.dependencies.findAll {dep -> dep.scope != 'test' } // removes the test scoped ones + } + } +} \ No newline at end of file diff --git a/plugins/groovy/src/main/groovy/org/elasticsearch/groovy/client/GAdminClient.groovy b/plugins/groovy/src/main/groovy/org/elasticsearch/groovy/client/GAdminClient.groovy new file mode 100644 index 00000000000..29b539037d7 --- /dev/null +++ b/plugins/groovy/src/main/groovy/org/elasticsearch/groovy/client/GAdminClient.groovy @@ -0,0 +1,22 @@ +package org.elasticsearch.groovy.client + +import org.elasticsearch.client.internal.InternalClient + +/** + * @author kimchy (shay.banon) + */ +class GAdminClient { + + private final InternalClient internalClient; + + final GIndicesAdminClient indices; + + final GClusterAdminClient cluster; + + def GAdminClient(internalClient) { + this.internalClient = internalClient; + + this.indices = new GIndicesAdminClient(internalClient) + this.cluster = new GClusterAdminClient(internalClient) + } +} diff --git a/plugins/groovy/src/main/groovy/org/elasticsearch/groovy/client/GClient.groovy b/plugins/groovy/src/main/groovy/org/elasticsearch/groovy/client/GClient.groovy new file mode 100644 index 00000000000..c68bb74310c --- /dev/null +++ b/plugins/groovy/src/main/groovy/org/elasticsearch/groovy/client/GClient.groovy @@ -0,0 +1,111 @@ +/* + * 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.groovy.client + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.delete.DeleteRequest +import org.elasticsearch.action.delete.DeleteResponse +import org.elasticsearch.action.get.GetRequest +import org.elasticsearch.action.get.GetResponse +import org.elasticsearch.action.index.IndexRequest +import org.elasticsearch.action.index.IndexResponse +import org.elasticsearch.client.Client +import org.elasticsearch.client.internal.InternalClient +import org.elasticsearch.groovy.client.action.GActionFuture +import org.elasticsearch.groovy.util.json.JsonBuilder + +/** + * @author kimchy (shay.banon) + */ +class GClient { + + static { + IndexRequest.metaClass.setSource = {Closure c -> + delegate.source(new JsonBuilder().buildAsBytes(c)) + } + IndexRequest.metaClass.source = {Closure c -> + delegate.source(new JsonBuilder().buildAsBytes(c)) + } + } + + final Client client; + + private final InternalClient internalClient + + final GAdminClient admin; + + def GClient(client) { + this.client = client; + this.internalClient = client; + + this.admin = new GAdminClient(internalClient) + } + + GActionFuture index(Closure c) { + IndexRequest request = new IndexRequest() + c.setDelegate request + c.call() + index(request) + } + + GActionFuture index(IndexRequest request) { + GActionFuture future = new GActionFuture(internalClient.threadPool(), request); + client.index(request, future) + return future + } + + void index(IndexRequest request, ActionListener listener) { + client.index(request, listener) + } + + GActionFuture get(Closure c) { + GetRequest request = new GetRequest() + c.setDelegate request + c.call() + get(request) + } + + GActionFuture get(GetRequest request) { + GActionFuture future = new GActionFuture(internalClient.threadPool(), request); + client.get(request, future) + return future + } + + void get(GetRequest request, ActionListener listener) { + client.get(request, listener) + } + + GActionFuture delete(Closure c) { + DeleteRequest request = new DeleteRequest() + c.setDelegate request + c.call() + delete(request) + } + + GActionFuture delete(DeleteRequest request) { + GActionFuture future = new GActionFuture(internalClient.threadPool(), request); + client.delete(request, future) + return future + } + + void delete(DeleteRequest request, ActionListener listener) { + client.delete(request, listener) + } +} diff --git a/plugins/groovy/src/main/groovy/org/elasticsearch/groovy/client/GClusterAdminClient.groovy b/plugins/groovy/src/main/groovy/org/elasticsearch/groovy/client/GClusterAdminClient.groovy new file mode 100644 index 00000000000..7d0e876a259 --- /dev/null +++ b/plugins/groovy/src/main/groovy/org/elasticsearch/groovy/client/GClusterAdminClient.groovy @@ -0,0 +1,19 @@ +package org.elasticsearch.groovy.client + +import org.elasticsearch.client.ClusterAdminClient +import org.elasticsearch.client.internal.InternalClient + +/** + * @author kimchy (shay.banon) + */ +class GClusterAdminClient { + + private final InternalClient internalClient; + + private final ClusterAdminClient clusterAdminClient; + + def GClusterAdminClient(internalClient) { + this.internalClient = internalClient; + this.clusterAdminClient = internalClient.admin().cluster(); + } +} diff --git a/plugins/groovy/src/main/groovy/org/elasticsearch/groovy/client/GIndicesAdminClient.groovy b/plugins/groovy/src/main/groovy/org/elasticsearch/groovy/client/GIndicesAdminClient.groovy new file mode 100644 index 00000000000..238ac9b846a --- /dev/null +++ b/plugins/groovy/src/main/groovy/org/elasticsearch/groovy/client/GIndicesAdminClient.groovy @@ -0,0 +1,40 @@ +package org.elasticsearch.groovy.client + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.admin.indices.refresh.RefreshRequest +import org.elasticsearch.action.admin.indices.refresh.RefreshResponse +import org.elasticsearch.client.IndicesAdminClient +import org.elasticsearch.client.internal.InternalClient +import org.elasticsearch.groovy.client.action.GActionFuture + +/** + * @author kimchy (shay.banon) + */ +class GIndicesAdminClient { + + private final InternalClient internalClient; + + private final IndicesAdminClient indicesAdminClient; + + def GIndicesAdminClient(internalClient) { + this.internalClient = internalClient; + this.indicesAdminClient = internalClient.admin().indices(); + } + + GActionFuture refresh(Closure c) { + RefreshRequest request = new RefreshRequest() + c.setDelegate request + c.call() + refresh(request) + } + + GActionFuture refresh(RefreshRequest request) { + GActionFuture future = new GActionFuture(internalClient.threadPool(), request); + indicesAdminClient.refresh(request, future) + return future + } + + void refresh(RefreshRequest request, ActionListener listener) { + indicesAdminClient.refresh(request, listener) + } +} diff --git a/plugins/groovy/src/main/groovy/org/elasticsearch/groovy/client/action/GActionFuture.java b/plugins/groovy/src/main/groovy/org/elasticsearch/groovy/client/action/GActionFuture.java new file mode 100644 index 00000000000..2f64a49aba6 --- /dev/null +++ b/plugins/groovy/src/main/groovy/org/elasticsearch/groovy/client/action/GActionFuture.java @@ -0,0 +1,96 @@ +/* + * 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.groovy.client.action; + +import groovy.lang.Closure; +import org.elasticsearch.ElasticSearchException; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.ActionRequest; +import org.elasticsearch.action.support.PlainListenableActionFuture; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.util.TimeValue; + +import java.util.concurrent.TimeUnit; + +/** + * @author kimchy (shay.banon) + */ +public class GActionFuture extends PlainListenableActionFuture { + + protected GActionFuture(ThreadPool threadPool, ActionRequest request) { + super(request.listenerThreaded(), threadPool); + } + + public void setListener(final Closure listener) { + addListener(new ActionListener() { + @Override public void onResponse(T t) { + listener.call(this); + } + + @Override public void onFailure(Throwable e) { + listener.call(this); + } + }); + } + + public void setSuccess(final Closure success) { + addListener(new ActionListener() { + @Override public void onResponse(T t) { + success.call(t); + } + + @Override public void onFailure(Throwable e) { + // ignore + } + }); + } + + public void setFailure(final Closure failure) { + addListener(new ActionListener() { + @Override public void onResponse(T t) { + // nothing + } + + @Override public void onFailure(Throwable e) { + failure.call(e); + } + }); + } + + public T getResponse() { + return actionGet(); + } + + public T response(String timeout) throws ElasticSearchException { + return super.actionGet(timeout); + } + + public T response(long timeoutMillis) throws ElasticSearchException { + return super.actionGet(timeoutMillis); + } + + public T response(TimeValue timeout) throws ElasticSearchException { + return super.actionGet(timeout); + } + + public T response(long timeout, TimeUnit unit) throws ElasticSearchException { + return super.actionGet(timeout, unit); + } +} diff --git a/plugins/groovy/src/main/groovy/org/elasticsearch/groovy/node/GNode.groovy b/plugins/groovy/src/main/groovy/org/elasticsearch/groovy/node/GNode.groovy new file mode 100644 index 00000000000..35d59f5428d --- /dev/null +++ b/plugins/groovy/src/main/groovy/org/elasticsearch/groovy/node/GNode.groovy @@ -0,0 +1,50 @@ +package org.elasticsearch.groovy.node + +import org.elasticsearch.groovy.client.GClient +import org.elasticsearch.node.Node + +/** + * @author kimchy (shay.banon) + */ +class GNode { + + final Node node; + + final GClient client; + + def GNode(Node node) { + this.node = node; + this.client = new GClient(node.client()) + } + + /** + * The settings that were used to create the node. + */ + def getSettings() { + node.settings(); + } + + /** + * Start the node. If the node is already started, this method is no-op. + */ + def getStart() { + node.start() + this + } + + /** + * Stops the node. If the node is already started, this method is no-op. + */ + def getStop() { + node.stop() + this + } + + /** + * Closes the node (and {@link #stop} s if its running). + */ + def getClose() { + node.close() + this + } +} diff --git a/plugins/groovy/src/main/groovy/org/elasticsearch/groovy/node/GNodeBuilder.groovy b/plugins/groovy/src/main/groovy/org/elasticsearch/groovy/node/GNodeBuilder.groovy new file mode 100644 index 00000000000..a363623631b --- /dev/null +++ b/plugins/groovy/src/main/groovy/org/elasticsearch/groovy/node/GNodeBuilder.groovy @@ -0,0 +1,35 @@ +package org.elasticsearch.groovy.node + +import org.elasticsearch.groovy.util.json.JsonBuilder +import org.elasticsearch.node.Node +import org.elasticsearch.node.internal.InternalNode +import org.elasticsearch.util.settings.ImmutableSettings +import org.elasticsearch.util.settings.loader.JsonSettingsLoader + +/** + * @author kimchy (shay.banon) + */ +public class GNodeBuilder { + + private final ImmutableSettings.Builder settingsBuilder = ImmutableSettings.settingsBuilder(); + + private boolean loadConfigSettings = true; + + public static GNodeBuilder nodeBuilder() { + new GNodeBuilder() + } + + def settings(Closure settings) { + byte[] settingsBytes = new JsonBuilder().buildAsBytes(settings); + settingsBuilder.put(new JsonSettingsLoader().load(settingsBytes)) + } + + def getBuild() { + Node node = new InternalNode(settingsBuilder.build(), loadConfigSettings) + new GNode(node) + } + + def getNode() { + build.start + } +} diff --git a/plugins/groovy/src/main/groovy/org/elasticsearch/groovy/util/json/JsonBuilder.groovy b/plugins/groovy/src/main/groovy/org/elasticsearch/groovy/util/json/JsonBuilder.groovy new file mode 100644 index 00000000000..1d31817cfbd --- /dev/null +++ b/plugins/groovy/src/main/groovy/org/elasticsearch/groovy/util/json/JsonBuilder.groovy @@ -0,0 +1,176 @@ +package org.elasticsearch.groovy.util.json + +import org.elasticsearch.ElasticSearchGenerationException +import org.elasticsearch.util.io.FastByteArrayOutputStream +import org.elasticsearch.util.io.FastCharArrayWriter +import static org.elasticsearch.util.json.Jackson.* + +/** + * Used to build JSON data. + * + * @author Marc Palmer + * @author Graeme Rocher + * + * @since 1.2 + */ +class JsonBuilder { + + static NODE_ELEMENT = "element" + + def root + + def current + + def nestingStack = [] + + def build(Closure c) { + return buildRoot(c) + } + + String buildAsString(Closure c) { + FastCharArrayWriter writer = FastCharArrayWriter.Cached.cached(); + try { + def json = build(c) + defaultObjectMapper().writeValue(writer, json); + } catch (IOException e) { + throw new ElasticSearchGenerationException("Failed to generate [" + c + "]", e); + } + return writer.toStringTrim() + } + + byte[] buildAsBytes(Closure c) { + FastByteArrayOutputStream os = FastByteArrayOutputStream.Cached.cached(); + try { + def json = build(c) + defaultObjectMapper().writeValue(os, json); + } catch (IOException e) { + throw new ElasticSearchGenerationException("Failed to generate [" + c + "]", e); + } + return os.copiedByteArray() + } + + private buildRoot(Closure c) { + c.delegate = this + //c.resolveStrategy = Closure.DELEGATE_FIRST + root = [:] + current = root + def returnValue = c.call() + if (!root) { + return returnValue + } + return root + } + + def invokeMethod(String methodName) { + current[methodName] = [] + } + + List array(Closure c) { + def prev = current + def list = [] + try { + + current = list + c.call(list) + } + finally { + current = prev + } + return list + } + + def invokeMethod(String methodName, Object args) { + if (args.size()) { + if (args[0] instanceof Map) { + // switch root to an array if elements used at top level + if ((current == root) && (methodName == NODE_ELEMENT) && !(root instanceof List)) { + if (root.size()) { + throw new IllegalArgumentException('Cannot have array elements in root node if properties of root have already been set') + } else { + root = [] + current = root + } + } + def n = [:] + if (current instanceof List) { + current << n + } else { + current[methodName] = n + } + n.putAll(args[0]) + } else if (args[-1] instanceof Closure) { + final Object callable = args[-1] + handleClosureNode(methodName, callable) + } else if (args.size() == 1) { + if (methodName != NODE_ELEMENT) { + throw new IllegalArgumentException('Array elements must be defined with the "element" method call eg: element(value)') + } + // switch root to an array if elements used at top level + if (current == root) { + if (root.size() && methodName != NODE_ELEMENT) { + throw new IllegalArgumentException('Cannot have array elements in root node if properties of root have already been set') + } else if (!(root instanceof List)) { + root = [] + current = root + } + } + if (current instanceof List) { + current << args[0] + } else { + throw new IllegalArgumentException('Array elements can only be defined under "array" nodes') + } + } else { + throw new IllegalArgumentException("This builder does not support invocation of [$methodName] with arg list ${args.dump()}") + } + } else { + current[methodName] = [] + } + } + + private handleClosureNode(String methodName, callable) { + def n = [:] + nestingStack << current + + if (current instanceof List) { + current << n + } + else { + current[methodName] = n + } + current = n + callable.call() + current = nestingStack.pop() + } + + + void setProperty(String propName, Object value) { + if (value instanceof Closure) { + handleClosureNode(propName, value) + } + else if (value instanceof List) { + value = value.collect { + if (it instanceof Closure) { + def callable = it + final JsonBuilder localBuilder = new JsonBuilder() + callable.delegate = localBuilder + callable.resolveStrategy = Closure.DELEGATE_FIRST + final Map nestedObject = localBuilder.buildRoot(callable) + return nestedObject + } + else { + return it + } + + } + current[propName] = value + } + else { + current[propName] = value + } + } + + def getProperty(String propName) { + current[propName] + } + +} \ No newline at end of file diff --git a/plugins/groovy/src/test/groovy/log4j.properties b/plugins/groovy/src/test/groovy/log4j.properties new file mode 100644 index 00000000000..86d72561ccf --- /dev/null +++ b/plugins/groovy/src/test/groovy/log4j.properties @@ -0,0 +1,21 @@ +log4j.rootLogger=INFO, out +log4j.logger.jgroups=WARN + +#log4j.logger.discovery=TRACE +#log4j.logger.cluster.service=TRACE +#log4j.logger.cluster.action.shard=DEBUG +#log4j.logger.indices.cluster=DEBUG +#log4j.logger.index=TRACE +#log4j.logger.index.engine=DEBUG +#log4j.logger.index.shard.service=DEBUG +#log4j.logger.index.shard.recovery=DEBUG +#log4j.logger.index.cache=DEBUG +#log4j.logger.http=TRACE +#log4j.logger.monitor.memory=TRACE +#log4j.logger.monitor.memory=TRACE +#log4j.logger.cluster.action.shard=TRACE +#log4j.logger.index.gateway=TRACE + +log4j.appender.out=org.apache.log4j.ConsoleAppender +log4j.appender.out.layout=org.apache.log4j.PatternLayout +log4j.appender.out.layout.ConversionPattern=[%d{ABSOLUTE}][%-5p][%-25c] %m%n diff --git a/plugins/groovy/src/test/groovy/org/elasticsearch/groovy/test/client/SimpleActionsTests.groovy b/plugins/groovy/src/test/groovy/org/elasticsearch/groovy/test/client/SimpleActionsTests.groovy new file mode 100644 index 00000000000..dee75dd97c9 --- /dev/null +++ b/plugins/groovy/src/test/groovy/org/elasticsearch/groovy/test/client/SimpleActionsTests.groovy @@ -0,0 +1,136 @@ +package org.elasticsearch.groovy.test.client + +import java.util.concurrent.CountDownLatch +import org.elasticsearch.action.index.IndexRequest +import org.elasticsearch.action.index.IndexResponse +import org.elasticsearch.groovy.node.GNode +import org.elasticsearch.groovy.node.GNodeBuilder +import static org.elasticsearch.client.Requests.* + +/** + * @author kimchy (shay.banon) + */ +class SimpleActionsTests extends GroovyTestCase { + + void testSimpleOperations() { + GNodeBuilder nodeBuilder = new GNodeBuilder() + nodeBuilder.settings { + node { + local = true + } + } + + GNode node = nodeBuilder.node + + def response = node.client.index(new IndexRequest( + index: "test", + type: "type1", + id: "1", + source: { + test = "value" + complex { + value1 = "value1" + value2 = "value2" + } + })).response + assertEquals "test", response.index + assertEquals "type1", response.type + assertEquals "1", response.id + + def refresh = node.client.admin.indices.refresh {} + assertEquals 0, refresh.response.failedShards + + def getR = node.client.get { + index "test" + type "type1" + id "1" + } + assertTrue getR.response.exists + assertEquals "test", getR.response.index + assertEquals "type1", getR.response.type + assertEquals "1", getR.response.id + assertEquals '{"test":"value","complex":{"value1":"value1","value2":"value2"}}', getR.response.sourceAsString() + assertEquals "value", getR.response.source.test + assertEquals "value1", getR.response.source.complex.value1 + + response = node.client.index({ + index = "test" + type = "type1" + id = "1" + source = { + test = "value" + complex { + value1 = "value1" + value2 = "value2" + } + } + }).response + assertEquals "test", response.index + assertEquals "type1", response.type + assertEquals "1", response.id + + def indexR = node.client.index(indexRequest().with { + index "test" + type "type1" + id "1" + source { + test = "value" + complex { + value1 = "value1" + value2 = "value2" + } + } + }) + CountDownLatch latch = new CountDownLatch(1) + indexR.success = {IndexResponse responseX -> + assertEquals "test", responseX.index + assertEquals "test", indexR.response.index + assertEquals "type1", responseX.type + assertEquals "type1", indexR.response.type + assertEquals "1", responseX.id + assertEquals "1", indexR.response.id + latch.countDown() + } + latch.await() + + indexR = node.client.index { + index "test" + type "type1" + id "1" + source { + test = "value" + complex { + value1 = "value1" + value2 = "value2" + } + } + } + latch = new CountDownLatch(1) + indexR.listener = { + assertEquals "test", indexR.response.index + assertEquals "type1", indexR.response.type + assertEquals "1", indexR.response.id + latch.countDown() + } + latch.await() + + def delete = node.client.delete { + index "test" + type "type1" + id "1" + } + assertEquals "test", delete.response.index + assertEquals "type1", delete.response.type + assertEquals "1", delete.response.id + + refresh = node.client.admin.indices.refresh {} + assertEquals 0, refresh.response.failedShards + + getR = node.client.get { + index "test" + type "type1" + id "1" + } + assertFalse getR.response.exists + } +} diff --git a/plugins/groovy/src/test/groovy/org/elasticsearch/groovy/test/node/GNodeBuilderTests.groovy b/plugins/groovy/src/test/groovy/org/elasticsearch/groovy/test/node/GNodeBuilderTests.groovy new file mode 100644 index 00000000000..10725f84a58 --- /dev/null +++ b/plugins/groovy/src/test/groovy/org/elasticsearch/groovy/test/node/GNodeBuilderTests.groovy @@ -0,0 +1,25 @@ +package org.elasticsearch.groovy.test.node + +import org.elasticsearch.groovy.node.GNode +import org.elasticsearch.groovy.node.GNodeBuilder +import static org.elasticsearch.groovy.node.GNodeBuilder.* + +/** + * @author kimchy (shay.banon) + */ +class GNodeBuilderTests extends GroovyTestCase { + + void testGNodeBuilder() { + GNodeBuilder nodeBuilder = nodeBuilder(); + nodeBuilder.settings { + node { + local = true + } + cluster { + name = "test" + } + } + GNode node = nodeBuilder.node + node.stop.close + } +} diff --git a/plugins/groovy/src/test/groovy/org/elasticsearch/groovy/util/json/JsonBuilderTests.groovy b/plugins/groovy/src/test/groovy/org/elasticsearch/groovy/util/json/JsonBuilderTests.groovy new file mode 100644 index 00000000000..ed956526834 --- /dev/null +++ b/plugins/groovy/src/test/groovy/org/elasticsearch/groovy/util/json/JsonBuilderTests.groovy @@ -0,0 +1,148 @@ +package org.elasticsearch.groovy.util.json + +/** + * @author kimchy (shay.banon) + */ +class JsonBuilderTests extends GroovyTestCase { + + void testSimple() { + def builder = new JsonBuilder() + + def result = builder.buildAsString { + rootprop = "something" + } + + assertEquals '{"rootprop":"something"}', result.toString() + } + + void testArrays() { + def builder = new JsonBuilder() + + def result = builder.buildAsString { + categories = ['a', 'b', 'c'] + rootprop = "something" + } + + assertEquals '{"categories":["a","b","c"],"rootprop":"something"}', result.toString() + } + + void testSubObjects() { + def builder = new JsonBuilder() + + def result = builder.buildAsString { + categories = ['a', 'b', 'c'] + rootprop = "something" + test { + subprop = 10 + } + } + + assertEquals '{"categories":["a","b","c"],"rootprop":"something","test":{"subprop":10}}', result.toString() + } + + void testAssignedObjects() { + def builder = new JsonBuilder() + + def result = builder.buildAsString { + categories = ['a', 'b', 'c'] + rootprop = "something" + test = { + subprop = 10 + } + } + + assertEquals '{"categories":["a","b","c"],"rootprop":"something","test":{"subprop":10}}', result.toString() + } + + void testNamedArgumentHandling() { + def builder = new JsonBuilder() + def result = builder.buildAsString { + categories = ['a', 'b', 'c'] + rootprop = "something" + test subprop: 10, three: [1, 2, 3] + + } + + assertEquals '{"categories":["a","b","c"],"rootprop":"something","test":{"subprop":10,"three":[1,2,3]}}', result.toString() + } + + + void testArrayOfClosures() { + def builder = new JsonBuilder() + def result = builder.buildAsString { + foo = [{ bar = "hello" }] + } + + assertEquals '{"foo":[{"bar":"hello"}]}', result.toString() + } + + void testRootElementList() { + def builder = new JsonBuilder() + + def results = ['one', 'two', 'three'] + + def result = builder.buildAsString { + for (b in results) { + element b + } + } + + assertEquals '["one","two","three"]', result.toString() + + result = builder.buildAsString { + results + } + + assertEquals '["one","two","three"]', result.toString() + + } + + void testExampleFromReferenceGuide() { + def builder = new JsonBuilder() + + def results = ['one', 'two', 'three'] + + def result = builder.buildAsString { + for (b in results) { + element title: b + } + } + + assertEquals '[{"title":"one"},{"title":"two"},{"title":"three"}]', result.toString() + + + result = builder.buildAsString { + books = results.collect { + [title: it] + } + } + + assertEquals '{"books":[{"title":"one"},{"title":"two"},{"title":"three"}]}', result.toString() + + result = builder.buildAsString { + books = array { + for (b in results) { + book title: b + } + } + } + + assertEquals '{"books":[{"title":"one"},{"title":"two"},{"title":"three"}]}', result.toString() + } + + void testAppendToArray() { + def builder = new JsonBuilder() + + def results = ['one', 'two', 'three'] + + def result = builder.buildAsString { + books = array {list -> + for (b in results) { + list << [title: b] + } + } + } + + assertEquals '{"books":[{"title":"one"},{"title":"two"},{"title":"three"}]}', result.toString() + } +} diff --git a/settings.gradle b/settings.gradle index 63a2539b0e0..c7fbb21f713 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,6 +7,7 @@ include 'test-integration' include 'benchmark-micro' include 'plugins-attachments' +include 'plugins-groovy' rootProject.name = 'elasticsearch-root' rootProject.children.each {project ->