public class BaseJresTest {

    @BeforeClass
    public static void startLocalElasticSearch() {
        ElasticSearchTestInstance.triggerStaticInit();
    }

    /**
     * Configured to connect to a local ElasticSearch instance created specifically for unit testing
     */
    protected Jres jres = new Jres(Suppliers.ofInstance("http://localhost:9201"));

} @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
public interface JresBulkable {

    /**
     * @return bulk command
     */
    Object getAction();

    /**
     * @return (optional) payload of action, e.g. index operation is followed by the document to be indexed
     */
    Object getPayload();

    /**
     * @return target ElasticSearch index
     */
    String getIndex();

    /**
     * @return target ElasticSearch type
     */
    String getType();

    /*
     * @return target document id
     */
    String getId();

    /**
     * In jackson serialization, the darned @JsonTypeInfo annotation is not dynamically looked up based on runtime type,
     * but declared type. In the case where JresBulkables are type-erased (such as in collections) the declared type
     * becomes effectively Object which means no @JsonTypeInfo. The serialization includes no type information.
     * Jackson could have <strong>easily</strong> examined the runtime types for @JsonTypeInfo annotations, but they
     * just didn't. So subclasses must implement this 'special' property to be picked up on the other end for
     * proper polymorphic deserialization.
     * <p/>
     * Realize that this means Jackson deserialization has NO issues with finding the @JsonTypeInfo annotation on a
     * type-erased generic type. This is only a workaround for the shortcoming of the serialization side.
     *
     * @return the fully-qualified name of the sub-class
     */
    @JsonProperty("@class")
    String getJsonTypeInfo();
} public class JresCreateIndex extends JresJsonRequest<JresAcknowledgedReply> {

    private final String index;
    private final Object settings;

    public JresCreateIndex(String index) {
        this(index, null);
    }

    public JresCreateIndex(String index, String settingsJson) {
        super(JresAcknowledgedReply.class);
        this.index = index;
        this.settings = settingsJson;
    }

    @Override
    public String getHttpMethod() {
        return HttpPut.METHOD_NAME;
    }

    @Override
    public String getPath() {
        return index;
    }

    @Override
    public Object getPayload() {
        return settings;
    }

} @JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE)
public class JresIndexDocument extends JresJsonRequest<JresIndexDocumentReply> implements JresBulkable {

    private final @Nullable String index;
    private final @Nullable String type;

    private final @Nullable String id;
    private final Object document;

    private final Boolean createOnly;

    /**
     * Index a document with no specified id. ElasticSearch will generate one. `index` or `type` are nullable
     * if this operation is to be included in a {@link JresBulk} request which specifies a default index or type,
     * respectively.
     */
    public JresIndexDocument(@Nullable String index, @Nullable String type, Object document) {
        this(index, type, null, document);
    }

    /**
     * Index a document with a specified id. ElasticSearch will replace any existing document with this id.
     * `index` or `type` are nullable if this operation is to be included in a {@link JresBulk} request which specifies
     * a default index or type, respectively.
     */
    public JresIndexDocument(@Nullable String index, @Nullable String type, @Nullable String id, Object document) {
        this(index, type, id, document, false);
    }

    /**
     * Index a document with the specified id. If <code>`create`</code> ElasticSearch will error on an attempt to
     * update an existing document at the given id. `index` or `type` are nullable if this operation is to be included
     * in a {@link JresBulk} request which specifies a default index or type, respectively.
     */
    @JsonCreator
    public JresIndexDocument(@JsonProperty("index") @Nullable String index,
                             @JsonProperty("type") @Nullable String type,
                             @JsonProperty("id") @Nullable String id,
                             @JsonProperty("document") Object document,
                             @JsonProperty("createOnly") boolean createOnly) {
        super(JresIndexDocumentReply.class);
        this.index = index;
        this.type = type;
        this.id = id;
        this.document = document;
        this.createOnly = createOnly;
    }

    @Override
    @Nullable
    @JsonProperty
    public String getIndex() {
        return index;
    }

    @Override
    @Nullable
    @JsonProperty
    public String getType() {
        return type;
    }

    @Override
    @Nullable
    @JsonProperty
    public String getId() {
        return id;
    }

    @JsonProperty
    public Object getDocument() {
        return document;
    }

    @JsonProperty
    public Boolean getCreateOnly() {
        return createOnly;
    }

    @Override
    public String getJsonTypeInfo() {
        return JresIndexDocument.class.getName();
    }

    @Override
    public String getHttpMethod() {
        return id == null ? HttpPost.METHOD_NAME : HttpPut.METHOD_NAME;
    }

    @Override
    public String getPath() {
        String path = slashedPath(index, type) + (id == null ? "" : id);
        if (createOnly) {
            path = slashedPath(path) + "?op_type=create";
        }
        return path;
    }

    @Override
    public Object getAction() {
        return ImmutableMap.of("index", NoNullsMap.of(
                "_index", index,
                "_type", type,
                "_id", id,
                "op_type", createOnly ? "create" : null
        ));
    }

    @Override
    public Object getPayload() {
        return document;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        JresIndexDocument that = (JresIndexDocument) o;

        if (createOnly != null ? !createOnly.equals(that.createOnly) : that.createOnly != null) return false;
        if (document != null ? !document.equals(that.document) : that.document != null) return false;
        if (id != null ? !id.equals(that.id) : that.id != null) return false;
        if (index != null ? !index.equals(that.index) : that.index != null) return false;
        if (type != null ? !type.equals(that.type) : that.type != null) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = index != null ? index.hashCode() : 0;
        result = 31 * result + (type != null ? type.hashCode() : 0);
        result = 31 * result + (id != null ? id.hashCode() : 0);
        result = 31 * result + (document != null ? document.hashCode() : 0);
        result = 31 * result + (createOnly != null ? createOnly.hashCode() : 0);
        return result;
    }
} public class JresRefresh extends JresJsonRequest<JresShardsReply> {

    private final String index;

    public JresRefresh(String index) {
        super(JresShardsReply.class);
        this.index = index;
    }

    @Override
    public String getHttpMethod() {
        return HttpPost.METHOD_NAME;
    }

    @Override
    public String getPath() {
        return JresPaths.slashedPath(index) + "_refresh";
    }

    @Override
    public Object getPayload() {
        return null;
    }
} public class JresPutMapping extends JresJsonRequest<JresAcknowledgedReply> {

    private final String index;
    private final String type;
    private final String mappingJson;

    public JresPutMapping(String index, String type) {
        this(index, type, String.format("{\"%s\":{}}", type));
    }

    public JresPutMapping(String index, String type, String mappingJson) {
        super(JresAcknowledgedReply.class);
        this.index = index;
        this.type = type;
        this.mappingJson = mappingJson;
    }

    @Override
    public String getHttpMethod() {
        return HttpPut.METHOD_NAME;
    }

    @Override
    public String getPath() {
        return JresPaths.slashedPath(index) + JresPaths.slashedPath(type) + "_mapping";
    }

    @Override
    public Object getPayload() {
        return mappingJson;
    }

} public class JresSearch extends JresJsonRequest<JresSearchReply> {

    private final @Nullable String index;
    private final @Nullable String type;
    private final Object searchBody;

    public JresSearch() {
        this(null, null, new JresSearchBody());
    }

    public JresSearch(JresSearchBody searchBody) {
        this(null, null, searchBody);
    }

    public JresSearch(@Nullable String index) {
        this(index, null, new JresSearchBody());
    }

    public JresSearch(@Nullable String index, @Nullable String type) {
        this(index, type, new JresSearchBody());
    }

    public JresSearch(@Nullable String index, @Nullable String type, JresSearchBody searchBody) {
        this(index, type, (Object) searchBody);
    }

    public JresSearch(@Nullable String index, @Nullable String type, Object searchBody) {
        super(JresSearchReply.class);
        this.searchBody = searchBody;
        this.index = index;
        this.type = type;
    }

    @Override
    public String getHttpMethod() {
        return HttpPost.METHOD_NAME;
    }

    @Override
    public String getPath() {
        return JresPaths.slashedPath(index) + JresPaths.slashedPath(type) + "_search";
    }

    @Override
    public Object getPayload() {
        return searchBody;
    }

} public class JresBulkItemResult {

    public static final String ACTION_CREATE = "create";
    public static final String ACTION_INDEX = "index";

    private final String action;
    private final Item result;

    public JresBulkItemResult(String action, Item result) {
        this.action = action;
        this.result = result;
    }

    public String getAction() {
        return action;
    }

    public Item getResult() {
        return result;
    }

    @Override
    public String toString() {
        return ObjectMappers.toJson(this);
    }
} public class JresBulkReply extends JresJsonReply {

    private Integer took;
    private List<Map<String, Item>> items;

    public List<String> keys() {
        return Lists.transform(items, new Function<Map<String, Item>, String>() {
            @Override
            public String apply(Map<String, Item> action) {
                assert action.size() == 1;
                return action.keySet().iterator().next();
            }
        });
    }

    public Integer getTook() {
        return took;
    }

    public List<Map<String, Item>> getItems() {
        return items;
    }

    public Iterable<JresBulkItemResult> getResults() {
        return Iterables.transform(node().get("items"), new Function<JsonNode, JresBulkItemResult>() {
            @Override
            public JresBulkItemResult apply(JsonNode resultEntry) {
                assert resultEntry.size() == 1;
                Map.Entry<String, JsonNode> result = resultEntry.fields().next();
                Item item = ObjectMappers.fromJson(result.getValue(), Item.class);
                return new JresBulkItemResult(result.getKey(), item);
            }
        });
    }

    public Iterable<JresBulkItemResult> getErrorResults() {
        return Iterables.filter(getResults(), new Predicate<JresBulkItemResult>() {
            @Override
            public boolean apply(JresBulkItemResult input) {
                return input.getResult().hasError();
            }
        });
    }
} public class JresSearchReply extends JresJsonReply {

    private Integer took;

    @JsonProperty("timed_out")
    private Boolean timedOut;

    @JsonProperty("_shards")
    private Shards shards;

    private Hits hits;

    private Facets facets;


    public Integer getTook() {
        return took;
    }

    public Boolean isTimedOut() {
        return timedOut;
    }

    public Shards getShards() {
        return shards;
    }

    public Hits getHits() {
        return hits;
    }

    public Facets getFacets() {
        return facets;
    }

    /**
     * Shortcut to {@link Hit#getSourceAsType(Class)}
     */
    public <T> List<T> getHitsAsType(final Class<T> klass) {
        return Lists.transform(getHits().getHits(), new Function<Hit, T>() {
            @Override
            public T apply(Hit hit) {
                return hit.getSourceAsType(klass);
            }
        });
    }

    /**
     * Shortcut to {@link Hit#getSourceAsType(TypeReference)}
     */
    public <T> List<T> getHitsAsType(final TypeReference<T> typeReference) {
        return Lists.transform(getHits().getHits(), new Function<Hit, T>() {
            @Override
            public T apply(Hit hit) {
                return hit.getSourceAsType(typeReference);
            }
        });
    }
}
import java.util.Iterator;
import com.blacklocus.jres.BaseJresTest;
import com.blacklocus.jres.request.JresBulkable;
import com.blacklocus.jres.request.index.JresCreateIndex;
import com.blacklocus.jres.request.index.JresIndexDocument;
import com.blacklocus.jres.request.index.JresRefresh;
import com.blacklocus.jres.request.mapping.JresPutMapping;
import com.blacklocus.jres.request.search.JresSearch;
import com.blacklocus.jres.response.bulk.JresBulkItemResult;
import com.blacklocus.jres.response.bulk.JresBulkReply;
import com.blacklocus.jres.response.search.JresSearchReply;
import com.google.common.collect.Iterables;
import org.junit.Assert;
import org.junit.Test;
import java.util.Arrays;
/**
 * Copyright 2015 BlackLocus
 *
 * Licensed 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 com.blacklocus.jres.request.bulk;



public class JresBulkTest extends BaseJresTest {

    @Test
    public void testHappy() {
        String index = "JresBulkRequestTest_happy".toLowerCase();
        String type = "test";

        jres.quest(new JresCreateIndex(index));