Skip to content

Commit e82b101

Browse files
authored
add support for new json object list output format (#58)
1 parent 16a8dbc commit e82b101

File tree

5 files changed

+273
-1
lines changed

5 files changed

+273
-1
lines changed

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Elasticsearch Data Format Plugin
44
## Overview
55

66
Elasticsearch Data Format Plugin provides a feature to allow you to download a response of a search result as several formats other than JSON.
7-
The supported formats are CSV, Excel and JSON(Bulk).
7+
The supported formats are CSV, Excel, JSON(Bulk) and JSON(Object List).
88

99
## Version
1010

@@ -67,3 +67,11 @@ If not, it's as scan query(all data are stored.).
6767
| bulk.index | string | Index name in Bulk file |
6868
| bulk.type | string | Type name in Bulk file |
6969

70+
### JSON (Object List format)
71+
72+
$ curl -o /tmp/data.json -XGET "localhost:9200/{index}/{type}/_data?format=jsonlist&source=..."
73+
74+
| Request Parameter | Type | Description |
75+
| :---------------- | :----: | :----------------------------------------------------------- |
76+
| source | string | [Query DSL](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl.html) |
77+

src/main/java/org/codelibs/elasticsearch/df/content/ContentType.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import org.codelibs.elasticsearch.df.content.csv.CsvContent;
44
import org.codelibs.elasticsearch.df.content.json.JsonContent;
5+
import org.codelibs.elasticsearch.df.content.json.JsonListContent;
56
import org.codelibs.elasticsearch.df.content.xls.XlsContent;
67
import org.elasticsearch.client.Client;
78
import org.elasticsearch.rest.RestRequest;
@@ -95,6 +96,27 @@ public String fileName(final RestRequest request) {
9596
}
9697
return index + ".xlsx";
9798
}
99+
},
100+
JSONLIST(50) {
101+
@Override
102+
public String contentType() {
103+
return "application/json";
104+
}
105+
106+
@Override
107+
public DataContent dataContent(final Client client,
108+
final RestRequest request) {
109+
return new JsonListContent(client, request, this);
110+
}
111+
112+
@Override
113+
public String fileName(final RestRequest request) {
114+
final String index = request.param("index");
115+
if (index == null) {
116+
return "_all.json";
117+
}
118+
return index + ".json";
119+
}
98120
};
99121

100122
private int index;
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package org.codelibs.elasticsearch.df.content.json;
2+
3+
import java.io.BufferedWriter;
4+
import java.io.File;
5+
import java.io.FileOutputStream;
6+
import java.io.IOException;
7+
import java.io.OutputStreamWriter;
8+
import java.io.Writer;
9+
10+
import org.apache.logging.log4j.LogManager;
11+
import org.apache.logging.log4j.Logger;
12+
import org.codelibs.elasticsearch.df.content.ContentType;
13+
import org.codelibs.elasticsearch.df.content.DataContent;
14+
import org.codelibs.elasticsearch.df.util.RequestUtil;
15+
import org.elasticsearch.ElasticsearchException;
16+
import org.elasticsearch.action.ActionListener;
17+
import org.elasticsearch.action.search.SearchResponse;
18+
import org.elasticsearch.client.Client;
19+
import org.elasticsearch.common.xcontent.XContentHelper;
20+
import org.elasticsearch.common.xcontent.XContentType;
21+
import org.elasticsearch.rest.RestChannel;
22+
import org.elasticsearch.rest.RestRequest;
23+
import org.elasticsearch.search.SearchHit;
24+
import org.elasticsearch.search.SearchHits;
25+
26+
public class JsonListContent extends DataContent {
27+
private static final Logger logger = LogManager.getLogger(JsonListContent.class);
28+
29+
public JsonListContent(final Client client, final RestRequest request, final ContentType contentType) {
30+
super(client, request, contentType);
31+
}
32+
33+
@Override
34+
public void write(final File outputFile, final SearchResponse response, final RestChannel channel,
35+
final ActionListener<Void> listener) {
36+
try {
37+
final OnLoadListener onLoadListener = new OnLoadListener(
38+
outputFile, listener);
39+
onLoadListener.onResponse(response);
40+
} catch (final Exception e) {
41+
listener.onFailure(new ElasticsearchException("Failed to write data.",
42+
e));
43+
}
44+
}
45+
46+
protected class OnLoadListener implements ActionListener<SearchResponse> {
47+
protected ActionListener<Void> listener;
48+
49+
protected Writer writer;
50+
51+
protected File outputFile;
52+
53+
private long currentCount = 0;
54+
55+
private boolean firstLine = true;
56+
57+
protected OnLoadListener(final File outputFile, final ActionListener<Void> listener) {
58+
this.outputFile = outputFile;
59+
this.listener = listener;
60+
try {
61+
writer = new BufferedWriter(new OutputStreamWriter(
62+
new FileOutputStream(outputFile), "UTF-8"));
63+
} catch (final Exception e) {
64+
throw new ElasticsearchException("Could not open "
65+
+ outputFile.getAbsolutePath(), e);
66+
}
67+
try {
68+
writer.append('[');
69+
}catch (final Exception e) {
70+
onFailure(e);
71+
}
72+
}
73+
74+
@Override
75+
public void onResponse(final SearchResponse response) {
76+
final String scrollId = response.getScrollId();
77+
final SearchHits hits = response.getHits();
78+
final int size = hits.getHits().length;
79+
currentCount += size;
80+
if (logger.isDebugEnabled()) {
81+
logger.debug("scrollId: {}, totalHits: {}, hits: {}, current: {}",
82+
scrollId, hits.getTotalHits(), size, currentCount);
83+
}
84+
try {
85+
for (final SearchHit hit : hits) {
86+
final String source = XContentHelper.convertToJson(
87+
hit.getSourceRef(), true, false, XContentType.JSON);
88+
if (!firstLine){
89+
writer.append(',');
90+
}else{
91+
firstLine = false;
92+
}
93+
writer.append('\n').append(source);
94+
}
95+
96+
if (size == 0 || scrollId == null) {
97+
// end
98+
writer.append('\n').append(']');
99+
writer.flush();
100+
close();
101+
listener.onResponse(null);
102+
} else {
103+
client.prepareSearchScroll(scrollId)
104+
.setScroll(RequestUtil.getScroll(request))
105+
.execute(this);
106+
}
107+
} catch (final Exception e) {
108+
onFailure(e);
109+
}
110+
}
111+
112+
@Override
113+
public void onFailure(final Exception e) {
114+
try {
115+
close();
116+
} catch (final Exception e1) {
117+
// ignore
118+
}
119+
listener.onFailure(new ElasticsearchException("Failed to write data.",
120+
e));
121+
}
122+
123+
private void close() {
124+
if (writer != null) {
125+
try {
126+
writer.close();
127+
} catch (final IOException e) {
128+
throw new ElasticsearchException("Could not close "
129+
+ outputFile.getAbsolutePath(), e);
130+
}
131+
}
132+
}
133+
}
134+
}

src/main/java/org/codelibs/elasticsearch/df/rest/RestDataAction.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ private ContentType getContentType(final RestRequest request) {
129129
|| "application/json".equals(contentType)
130130
|| "json".equalsIgnoreCase(contentType)) {
131131
return ContentType.JSON;
132+
} else if ("application/list+json".equals(contentType)
133+
|| "jsonlist".equals(contentType)) {
134+
return ContentType.JSONLIST;
132135
}
133136

134137
return null;

src/test/java/org/codelibs/elasticsearch/df/DataFormatPluginTest.java

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,21 @@ public class DataFormatPluginTest {
5252
private static final File csvTempFile;
5353
private static final File xlsTempFile;
5454
private static final File jsonTempFile;
55+
private static final File jsonListTempFile;
5556
private static final String path;
5657

5758
private final Map<String, String> paramsCsv = new HashMap<>();
5859
private final Map<String, String> paramsXls = new HashMap<>();
5960
private final Map<String, String> paramsJson = new HashMap<>();
61+
private final Map<String, String> paramsJsonList = new HashMap<>();
6062

6163
static {
6264
docNumber = 20;
6365

6466
csvTempFile = createTempFile("csvtest", ".csv");
6567
xlsTempFile = createTempFile("xlstest", ".xls");
6668
jsonTempFile = createTempFile("jsontest", ".json");
69+
jsonListTempFile = createTempFile("jsonlisttest", ".json");
6770
path = "/dataset0/_data";
6871
}
6972

@@ -106,13 +109,15 @@ public void prepareParams() {
106109
paramsCsv.put("format", "csv");
107110
paramsXls.put("format", "xls");
108111
paramsJson.put("format", "json");
112+
paramsJsonList.put("format", "jsonlist");
109113
}
110114

111115
@After
112116
public void clearParams() {
113117
paramsCsv.clear();
114118
paramsXls.clear();
115119
paramsJson.clear();
120+
paramsJsonList.clear();
116121
}
117122

118123
@Test
@@ -421,6 +426,106 @@ public void dumpJsonInFile() throws IOException {
421426
}
422427
}
423428

429+
@Test
430+
public void dumpJsonList() throws IOException {
431+
432+
// Download All as JSON
433+
try (CurlResponse curlResponse = EcrCurl.get(node, "/dataset0/_data")
434+
.header("Content-Type", "application/json")
435+
.param("format", "jsonlist").execute()) {
436+
final String content = curlResponse.getContentAsString();
437+
final String[] lines = content.split("\n");
438+
assertEquals(docNumber + 2, lines.length);
439+
assertTrue(lines[0].equals("["));
440+
assertTrue(lines[1].startsWith("{" + "\"aaa\":\"test"));
441+
assertTrue(lines[docNumber + 1].equals("]"));
442+
}
443+
444+
final String query = "{\"query\":{\"bool\":{\"must\":[{\"range\":{\"bbb\":{\"from\":\"1\",\"to\":\"10\"}}}],\"must_not\":[],\"should\":[]}},\"sort\":[\"bbb\"]}";
445+
446+
// Download 10 docs as JSON with Query
447+
try (CurlResponse curlResponse = EcrCurl.get(node, "/dataset0/_data")
448+
.header("Content-Type", "application/json")
449+
.param("format", "jsonlist")
450+
.param("search_type", "query_then_fetch").body(query)
451+
.execute()) {
452+
final String content = curlResponse.getContentAsString();
453+
final String[] lines = content.split("\n");
454+
assertEquals(10 + 2, lines.length);
455+
assertTrue(lines[0].startsWith("["));
456+
assertTrue(lines[1].startsWith("{" + "\"aaa\":\"test"));
457+
}
458+
459+
// Download 10 docs as JSON
460+
try (CurlResponse curlResponse = EcrCurl.get(node, "/dataset0/_data")
461+
.header("Content-Type", "application/json").param("q", "*:*")
462+
.param("format", "jsonlist").param("from", "5").execute()) {
463+
final String content = curlResponse.getContentAsString();
464+
final String[] lines = content.split("\n");
465+
assertEquals(15 + 2, lines.length);
466+
}
467+
468+
// Download all the docs from the 5th as JSON
469+
try (CurlResponse curlResponse = EcrCurl.get(node, "/dataset0/_data")
470+
.header("Content-Type", "application/json").param("q", "*:*")
471+
.param("format", "jsonlist").param("from", "5")
472+
.param("size", String.valueOf(docNumber)).execute()) {
473+
final String content = curlResponse.getContentAsString();
474+
final String[] lines = content.split("\n");
475+
assertEquals((docNumber - 5) + 2, lines.length);
476+
}
477+
478+
final String queryWithFrom = "{\"query\":{\"match_all\":{}},\"from\":5,\"size\":" + String.valueOf(docNumber) + ",\"sort\":[\"bbb\"]}";
479+
480+
// Download All as JSON with Query and from
481+
try (CurlResponse curlResponse = EcrCurl.get(node, "/dataset0/_data")
482+
.header("Content-Type", "application/json")
483+
.param("format", "jsonlist").body(queryWithFrom).execute()) {
484+
final String content = curlResponse.getContentAsString();
485+
final String[] lines = content.split("\n");
486+
assertEquals((docNumber - 5) + 2, lines.length);
487+
}
488+
489+
// Download All as JSON with Query and from
490+
try (CurlResponse curlResponse = EcrCurl.get(node, "/dataset0/_data")
491+
.header("Content-Type", "application/json")
492+
.param("format", "jsonlist").param("source", queryWithFrom)
493+
.param("source_content_type", "application/json")
494+
.execute()) {
495+
final String content = curlResponse.getContentAsString();
496+
final String[] lines = content.split("\n");
497+
assertEquals((docNumber - 5) + 2, lines.length);
498+
}
499+
500+
// Download All as JSON with search_type
501+
try (CurlResponse curlResponse = EcrCurl.get(node, "/dataset0/_data")
502+
.header("Content-Type", "application/json")
503+
.param("search_type", "query_then_fetch")
504+
.param("format", "jsonlist").execute()) {
505+
final String content = curlResponse.getContentAsString();
506+
final String[] lines = content.split("\n");
507+
assertEquals(docNumber + 2, lines.length);
508+
assertTrue(lines[0].equals("["));
509+
assertTrue(lines[1].startsWith("{" + "\"aaa\":\"test"));
510+
assertTrue(lines[docNumber + 1].equals("]"));
511+
}
512+
}
513+
514+
@Test
515+
public void dumpJsonListInFile() throws IOException {
516+
paramsJsonList.put("file", jsonListTempFile.getAbsolutePath());
517+
518+
try (CurlResponse curlResponse = createRequest(node, path, paramsJsonList).execute()) {
519+
assertAcknowledged(curlResponse, jsonListTempFile);
520+
final List<String> lines = Files.readAllLines(jsonListTempFile.toPath(), Charsets.UTF_8);
521+
assertEquals(docNumber + 2, lines.size());
522+
assertTrue(lines.get(0).equals("["));
523+
assertTrue(lines.get(1).startsWith("{" + "\"aaa\":\"test"));
524+
assertTrue(lines.get(docNumber).startsWith("{" + "\"aaa\":\"test"));
525+
assertTrue(lines.get(docNumber + 1).equals("]"));
526+
}
527+
}
528+
424529
@Test
425530
public void dumpSizeLimit() throws IOException {
426531

0 commit comments

Comments
 (0)