Skip to content
This repository was archived by the owner on Jan 8, 2019. It is now read-only.

Commit a807ffb

Browse files
author
Lennart Koopmann
committed
importing exported extractors. #726
1 parent ceaca33 commit a807ffb

File tree

11 files changed

+266
-18
lines changed

11 files changed

+266
-18
lines changed

app/controllers/ExtractorsController.java

Lines changed: 95 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,20 @@
2121
import com.fasterxml.jackson.core.JsonProcessingException;
2222
import com.fasterxml.jackson.databind.ObjectMapper;
2323
import com.google.common.collect.Lists;
24+
import com.google.common.collect.Maps;
2425
import com.google.inject.Inject;
2526
import lib.APIException;
2627
import lib.ApiClient;
2728
import lib.BreadcrumbList;
29+
import lib.Version;
2830
import models.*;
31+
import models.api.requests.ExtractorImportRequest;
32+
import models.api.requests.ExtractorListImportRequest;
2933
import play.Logger;
3034
import play.mvc.Result;
3135

3236
import java.io.IOException;
37+
import java.util.ArrayList;
3338
import java.util.List;
3439
import java.util.Map;
3540

@@ -166,23 +171,26 @@ public Result delete(String nodeId, String inputId, String extractorId) {
166171
}
167172
}
168173

169-
public Result export(String nodeId, String inputId) {
174+
public Result exportExtractors(String nodeId, String inputId) {
170175
try {
171176
Node node = nodeService.loadNode(nodeId);
172177
Input input = node.getInput(inputId);
173178

174179
BreadcrumbList bc = standardBreadcrumbs(node, input);
175-
bc.addCrumb("Export", routes.ExtractorsController.export(nodeId, inputId));
180+
bc.addCrumb("Export", routes.ExtractorsController.exportExtractors(nodeId, inputId));
176181

177-
List<Map<String, Object>> exports = Lists.newArrayList();
182+
Map<String, Object> result = Maps.newHashMap();
183+
List<Map<String, Object>> extractors = Lists.newArrayList();
178184
for (Extractor extractor : extractorService.all(node, input)) {
179-
exports.add(extractor.export());
185+
extractors.add(extractor.export());
180186
}
187+
result.put("extractors", extractors);
188+
result.put("version", Version.VERSION.toString());
181189

182190
String extractorExport = "[]";
183191
try {
184192
ObjectMapper om = new ObjectMapper();
185-
extractorExport = om.writeValueAsString(exports);
193+
extractorExport = om.writeValueAsString(result);
186194
} catch(JsonProcessingException e) {
187195
Logger.error("Could not generate extractor export.", e);
188196
}
@@ -204,6 +212,88 @@ public Result export(String nodeId, String inputId) {
204212
}
205213
}
206214

215+
public Result importExtractorsPage(String nodeId, String inputId) {
216+
try {
217+
Node node = nodeService.loadNode(nodeId);
218+
Input input = node.getInput(inputId);
219+
220+
BreadcrumbList bc = standardBreadcrumbs(node, input);
221+
bc.addCrumb("Import", routes.ExtractorsController.importExtractorsPage(nodeId, inputId));
222+
223+
return ok(views.html.system.inputs.extractors.importPage.render(
224+
currentUser(),
225+
bc,
226+
node,
227+
input
228+
));
229+
} catch (IOException e) {
230+
return status(500, views.html.errors.error.render(ApiClient.ERROR_MSG_IO, e, request()));
231+
} catch (APIException e) {
232+
String message = "Could not fetch system information. We expected HTTP 200, but got a HTTP " + e.getHttpCode() + ".";
233+
return status(500, views.html.errors.error.render(message, e, request()));
234+
} catch (NodeService.NodeNotFoundException e) {
235+
return status(404, views.html.errors.error.render(ApiClient.ERROR_MSG_NODE_NOT_FOUND, e, request()));
236+
}
237+
}
238+
239+
public Result importExtractors(String nodeId, String inputId) {
240+
Map<String, String> form = flattenFormUrlEncoded(request().body().asFormUrlEncoded());
241+
242+
if(!form.containsKey("extractors") || form.get("extractors").isEmpty()) {
243+
flash("error", "No JSON provided. Please fill out the import definition field.");
244+
return redirect(routes.ExtractorsController.importExtractorsPage(nodeId, inputId));
245+
}
246+
247+
ExtractorListImportRequest elir;
248+
try {
249+
ObjectMapper om = new ObjectMapper();
250+
elir = om.readValue(form.get("extractors"), ExtractorListImportRequest.class);
251+
} catch(Exception e) {
252+
Logger.error("Could not read JSON.", e);
253+
flash("error", "Could not read JSON.");
254+
return redirect(routes.ExtractorsController.importExtractorsPage(nodeId, inputId));
255+
}
256+
257+
/*
258+
* For future versions with breaking changes: check the "version" field in the ExtractorListImportRequest.
259+
*
260+
* Thank me later.
261+
*/
262+
263+
int successes = 0;
264+
for (ExtractorImportRequest importRequest : elir.extractors) {
265+
try {
266+
Node node = nodeService.loadNode(nodeId);
267+
268+
Extractor.Type type = Extractor.Type.valueOf(importRequest.extractorType.toUpperCase());
269+
270+
Extractor extractor = extractorFactory.forCreate(
271+
Extractor.CursorStrategy.valueOf(importRequest.cursorStrategy.toUpperCase()),
272+
importRequest.title,
273+
importRequest.sourceField,
274+
importRequest.targetField,
275+
type,
276+
currentUser(),
277+
Extractor.ConditionType.valueOf(importRequest.conditionType.toUpperCase()),
278+
importRequest.conditionValue
279+
);
280+
281+
extractor.loadConfigFromImport(type, importRequest.extractorConfig);
282+
extractor.loadConvertersFromImport(importRequest.converters);
283+
extractor.setOrder(importRequest.order);
284+
extractor.create(node, node.getInput(inputId));
285+
} catch (Exception e) {
286+
Logger.error("Could not import extractor. Continuing.", e);
287+
continue;
288+
}
289+
290+
successes++;
291+
}
292+
293+
flash("success", "Successfully imported " + successes + " of " + elir.extractors.size() + " extractors.");
294+
return redirect(routes.ExtractorsController.manage(nodeId, inputId));
295+
}
296+
207297
private static BreadcrumbList standardBreadcrumbs(Node node, Input input) {
208298
BreadcrumbList bc = new BreadcrumbList();
209299
bc.addCrumb("System", routes.SystemController.index(0));

app/models/Extractor.java

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.google.inject.assistedinject.AssistedInject;
2525
import lib.APIException;
2626
import lib.ApiClient;
27+
import lib.Version;
2728
import models.api.requests.CreateExtractorRequest;
2829
import models.api.responses.system.ExtractorSummaryResponse;
2930
import org.slf4j.Logger;
@@ -96,7 +97,6 @@ public static ConditionType fromString(String name) {
9697

9798
private String id;
9899
private final String title;
99-
private final int order;
100100
private final CursorStrategy cursorStrategy;
101101
private final Type extractorType;
102102
private final String sourceField;
@@ -110,6 +110,8 @@ public static ConditionType fromString(String name) {
110110
private final long exceptions;
111111
private final long converterExceptions;
112112

113+
private int order;
114+
113115
@AssistedInject
114116
private Extractor(ApiClient api, UserService userService, @Assisted ExtractorSummaryResponse esr) {
115117
this.api = api;
@@ -180,7 +182,8 @@ public Extractor create(Node node, Input input) throws IOException, APIException
180182
request.extractorConfig = extractorConfig;
181183
request.converters = converterList;
182184
request.conditionType = conditionType.toString().toLowerCase();
183-
request.conditionValue = conditionValue;
185+
request.conditionValue = conditionValue;
186+
request.order = order;
184187

185188
final Map response = api.post(Map.class)
186189
.path("/system/inputs/{0}/extractors", input.getId())
@@ -206,6 +209,17 @@ public void loadConfigFromForm(Type extractorType, Map<String,String[]> form) {
206209
}
207210
}
208211

212+
public void loadConfigFromImport(Type type, Map<String, Object> extractorConfig) {
213+
// we go the really easy way here.
214+
Map<String, String[]> looksLikeForm = Maps.newHashMap();
215+
216+
for (Map.Entry<String, Object> e : extractorConfig.entrySet()) {
217+
looksLikeForm.put(e.getKey(), new String[]{ e.getValue().toString() });
218+
}
219+
220+
loadConfigFromForm(type, looksLikeForm);
221+
}
222+
209223
public void loadConvertersFromForm(Map<String,String[]> form) {
210224
for(String name : extractSelectedConverters(form)) {
211225
Converter.Type converterType = Converter.Type.valueOf(name.toUpperCase());
@@ -215,6 +229,17 @@ public void loadConvertersFromForm(Map<String,String[]> form) {
215229
}
216230
}
217231

232+
public void loadConvertersFromImport(List<Map<String, Object>> imports) {
233+
for (Map<String, Object> imp : imports) {
234+
Converter converter = new Converter(
235+
Converter.Type.valueOf(((String) imp.get("type")).toUpperCase()),
236+
(Map<String, Object>) imp.get("config")
237+
);
238+
239+
converters.add(converter);
240+
}
241+
}
242+
218243
private Map<String, Object> extractConverterConfig(Converter.Type converterType, Map<String,String[]> form) {
219244
Map<String, Object> config = Maps.newHashMap();
220245
switch (converterType) {
@@ -394,12 +419,21 @@ public int getOrder() {
394419
return order;
395420
}
396421

422+
public void setOrder(int order) {
423+
this.order = order;
424+
}
425+
397426
public Map<String, Object> export() {
398427
Map<String, Object> export = Maps.newTreeMap();
399428

400429
List<Map<String, Object>> converterConfigList = Lists.newArrayList();
401430
for (Converter converter : converters) {
402-
converterConfigList.add(converter.getConfig());
431+
Map<String, Object> converterExport = Maps.newHashMap();
432+
433+
converterExport.put("config", converter.getConfig());
434+
converterExport.put("type", converter.getType());
435+
436+
converterConfigList.add(converterExport);
403437
}
404438

405439
export.put("title", title);

app/models/api/requests/CreateExtractorRequest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,6 @@ public class CreateExtractorRequest extends ApiRequest {
5555
@SerializedName("condition_value")
5656
public String conditionValue;
5757

58+
public int order;
59+
5860
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* Copyright 2014 Lennart Koopmann <[email protected]>
3+
*
4+
* This file is part of Graylog2.
5+
*
6+
* Graylog2 is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* Graylog2 is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with Graylog2. If not, see <http://www.gnu.org/licenses/>.
18+
*
19+
*/
20+
package models.api.requests;
21+
22+
import com.fasterxml.jackson.annotation.JsonProperty;
23+
24+
import java.util.List;
25+
import java.util.Map;
26+
27+
/**
28+
* @author Lennart Koopmann <[email protected]>
29+
*/
30+
public class ExtractorImportRequest {
31+
32+
public String title;
33+
34+
public int order;
35+
36+
@JsonProperty("condition_type")
37+
public String conditionType;
38+
39+
@JsonProperty("condition_value")
40+
public String conditionValue;
41+
42+
@JsonProperty("cursor_strategy")
43+
public String cursorStrategy;
44+
45+
@JsonProperty("extractor_type")
46+
public String extractorType;
47+
48+
@JsonProperty("source_field")
49+
public String sourceField;
50+
51+
@JsonProperty("target_field")
52+
public String targetField;
53+
54+
@JsonProperty("extractor_config")
55+
public Map<String, Object> extractorConfig;
56+
57+
public List<Map<String, Object>> converters;
58+
59+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* Copyright 2014 Lennart Koopmann <[email protected]>
3+
*
4+
* This file is part of Graylog2.
5+
*
6+
* Graylog2 is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* Graylog2 is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with Graylog2. If not, see <http://www.gnu.org/licenses/>.
18+
*
19+
*/
20+
package models.api.requests;
21+
22+
import java.util.List;
23+
import java.util.Map;
24+
25+
/**
26+
* @author Lennart Koopmann <[email protected]>
27+
*/
28+
public class ExtractorListImportRequest {
29+
30+
public List<ExtractorImportRequest> extractors;
31+
public String version;
32+
33+
}

app/views/system/inputs/extractors/export.scala.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@
66

77
<div class="row-fluid">
88
<h1>
9-
<i class="icon icon-cloud-download"></i>
9+
<i class="icon icon-cloud-upload"></i>
1010
Export extractors of <em>@input.getTitle</em>
1111
</h1>
1212

1313
The extractors of an input can be exported to JSON for importing into other setups
14-
or sharing in the extractor directory on <a href="http://www.graylog2.org/">graylog2.org</a>.
14+
or sharing in the extractor directory on <a href="http://www.graylog2.org/" target="_blank">graylog2.org</a>.
1515
</div>
1616

1717
<div class="row-fluid">
18-
<textarea id="extractor-export-json">@extractorExport</textarea>
18+
<textarea class="extractor-json">@extractorExport</textarea>
1919
</div>
2020

2121
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
@(currentUser: User, breadcrumbs: lib.BreadcrumbList, node: Node, input: Input)
2+
3+
@main("Import extractors", views.html.system.sidebar(), "", currentUser) {
4+
5+
@views.html.partials.breadcrumbs(breadcrumbs)
6+
7+
<div class="row-fluid">
8+
<h1>
9+
<i class="icon icon-cloud-download"></i>
10+
Import extractors to input <em>@input.getTitle</em>
11+
</h1>
12+
13+
Exported extractors can be imported to an input. All you need is the JSON export of extractors from any other
14+
Graylog2 setup or from the extractor directory on <a href="http://www.graylog2.org/" target="_blank">graylog2.org</a>.
15+
</div>
16+
17+
<form action="@routes.ExtractorsController.importExtractors(node.getNodeId, input.getId)" method="POST">
18+
<div class="row-fluid">
19+
<textarea class="extractor-json" name="extractors"></textarea>
20+
</div>
21+
22+
<button type="submit" class="btn btn-success" data-confirm="Really add all extractors to this input?">
23+
<i class="icon icon-cloud-download"></i>
24+
Add extractors to input
25+
</button>
26+
</form>
27+
28+
}

app/views/system/inputs/extractors/manage.scala.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
<span class="caret"></span>
1010
</a>
1111
<ul class="dropdown-menu">
12-
<li><a href="#">Import extractors</a></li>
13-
<li><a href="@routes.ExtractorsController.export(node.getNodeId, input.getId)">Export extractors</a></li>
12+
<li><a href="@routes.ExtractorsController.importExtractorsPage(node.getNodeId, input.getId)">Import extractors</a></li>
13+
<li><a href="@routes.ExtractorsController.exportExtractors(node.getNodeId, input.getId)">Export extractors</a></li>
1414
</ul>
1515
</div>
1616
</div>

0 commit comments

Comments
 (0)