7
7
8
8
import org .apache .logging .log4j .message .ParameterizedMessage ;
9
9
import org .apache .logging .log4j .util .Supplier ;
10
+ import org .elasticsearch .ElasticsearchException ;
10
11
import org .elasticsearch .ElasticsearchParseException ;
11
12
import org .elasticsearch .common .Nullable ;
13
+ import org .elasticsearch .common .Strings ;
12
14
import org .elasticsearch .common .unit .TimeValue ;
15
+ import org .elasticsearch .common .xcontent .ObjectPath ;
13
16
import org .elasticsearch .common .xcontent .ToXContentObject ;
14
17
import org .elasticsearch .common .xcontent .XContentBuilder ;
15
18
import org .elasticsearch .common .xcontent .XContentParser ;
30
33
import java .time .Clock ;
31
34
import java .time .ZoneOffset ;
32
35
import java .time .ZonedDateTime ;
36
+ import java .util .ArrayList ;
37
+ import java .util .Collection ;
38
+ import java .util .List ;
39
+ import java .util .Map ;
33
40
import java .util .Objects ;
41
+ import java .util .Set ;
42
+ import java .util .stream .Collectors ;
34
43
35
44
import static org .elasticsearch .common .unit .TimeValue .timeValueMillis ;
36
45
@@ -43,16 +52,20 @@ public class ActionWrapper implements ToXContentObject {
43
52
private final ExecutableTransform <Transform , Transform .Result > transform ;
44
53
private final ActionThrottler throttler ;
45
54
private final ExecutableAction <? extends Action > action ;
55
+ @ Nullable
56
+ private String path ;
46
57
47
58
public ActionWrapper (String id , ActionThrottler throttler ,
48
59
@ Nullable ExecutableCondition condition ,
49
60
@ Nullable ExecutableTransform <Transform , Transform .Result > transform ,
50
- ExecutableAction <? extends Action > action ) {
61
+ ExecutableAction <? extends Action > action ,
62
+ @ Nullable String path ) {
51
63
this .id = id ;
52
64
this .condition = condition ;
53
65
this .throttler = throttler ;
54
66
this .transform = transform ;
55
67
this .action = action ;
68
+ this .path = path ;
56
69
}
57
70
58
71
public String id () {
@@ -140,13 +153,64 @@ public ActionWrapperResult execute(WatchExecutionContext ctx) {
140
153
return new ActionWrapperResult (id , conditionResult , null , new Action .Result .FailureWithException (action .type (), e ));
141
154
}
142
155
}
143
- try {
144
- Action .Result actionResult = action .execute (id , ctx , payload );
145
- return new ActionWrapperResult (id , conditionResult , transformResult , actionResult );
146
- } catch (Exception e ) {
147
- action .logger ().error (
156
+ if (Strings .isEmpty (path )) {
157
+ try {
158
+ Action .Result actionResult = action .execute (id , ctx , payload );
159
+ return new ActionWrapperResult (id , conditionResult , transformResult , actionResult );
160
+ } catch (Exception e ) {
161
+ action .logger ().error (
162
+ (Supplier <?>) () -> new ParameterizedMessage ("failed to execute action [{}/{}]" , ctx .watch ().id (), id ), e );
163
+ return new ActionWrapperResult (id , new Action .Result .FailureWithException (action .type (), e ));
164
+ }
165
+ } else {
166
+ try {
167
+ List <Action .Result > results = new ArrayList <>();
168
+ Object object = ObjectPath .eval (path , ctx .payload ().data ());
169
+ if (object instanceof Collection ) {
170
+ Collection collection = Collection .class .cast (object );
171
+ if (collection .isEmpty ()) {
172
+ throw new ElasticsearchException ("foreach object [{}] was an empty list, could not run any action" , path );
173
+ } else {
174
+ for (Object o : collection ) {
175
+ if (o instanceof Map ) {
176
+ results .add (action .execute (id , ctx , new Payload .Simple ((Map <String , Object >) o )));
177
+ } else {
178
+ throw new ElasticsearchException ("item in foreach [{}] object was not a map" , path );
179
+ }
180
+ }
181
+ }
182
+ } else {
183
+ throw new ElasticsearchException ("specified foreach object was not a an array/collection: [{}]" , path );
184
+ }
185
+
186
+ // check if we have mixed results, then set to partial failure
187
+ final Set <Action .Result .Status > statuses = results .stream ().map (Action .Result ::status ).collect (Collectors .toSet ());
188
+ Action .Result .Status status ;
189
+ if (statuses .size () == 1 ) {
190
+ status = statuses .iterator ().next ();
191
+ } else {
192
+ status = Action .Result .Status .PARTIAL_FAILURE ;
193
+ }
194
+
195
+ return new ActionWrapperResult (id , conditionResult , transformResult ,
196
+ new Action .Result (action .type (), status ) {
197
+ @ Override
198
+ public XContentBuilder toXContent (XContentBuilder builder , Params params ) throws IOException {
199
+ builder .startArray (WatchField .FOREACH .getPreferredName ());
200
+ for (Action .Result result : results ) {
201
+ builder .startObject ();
202
+ result .toXContent (builder , params );
203
+ builder .endObject ();
204
+ }
205
+ builder .endArray ();
206
+ return builder ;
207
+ }
208
+ });
209
+ } catch (Exception e ) {
210
+ action .logger ().error (
148
211
(Supplier <?>) () -> new ParameterizedMessage ("failed to execute action [{}/{}]" , ctx .watch ().id (), id ), e );
149
- return new ActionWrapperResult (id , new Action .Result .FailureWithException (action .type (), e ));
212
+ return new ActionWrapperResult (id , new Action .Result .FailureWithException (action .type (), e ));
213
+ }
150
214
}
151
215
}
152
216
@@ -186,6 +250,9 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
186
250
.field (transform .type (), transform , params )
187
251
.endObject ();
188
252
}
253
+ if (Strings .isEmpty (path ) == false ) {
254
+ builder .field (WatchField .FOREACH .getPreferredName (), path );
255
+ }
189
256
builder .field (action .type (), action , params );
190
257
return builder .endObject ();
191
258
}
@@ -198,6 +265,7 @@ static ActionWrapper parse(String watchId, String actionId, XContentParser parse
198
265
ExecutableCondition condition = null ;
199
266
ExecutableTransform <Transform , Transform .Result > transform = null ;
200
267
TimeValue throttlePeriod = null ;
268
+ String path = null ;
201
269
ExecutableAction <? extends Action > action = null ;
202
270
203
271
String currentFieldName = null ;
@@ -208,6 +276,8 @@ static ActionWrapper parse(String watchId, String actionId, XContentParser parse
208
276
} else {
209
277
if (WatchField .CONDITION .match (currentFieldName , parser .getDeprecationHandler ())) {
210
278
condition = actionRegistry .getConditionRegistry ().parseExecutable (watchId , parser );
279
+ } else if (WatchField .FOREACH .match (currentFieldName , parser .getDeprecationHandler ())) {
280
+ path = parser .text ();
211
281
} else if (Transform .TRANSFORM .match (currentFieldName , parser .getDeprecationHandler ())) {
212
282
transform = actionRegistry .getTransformRegistry ().parse (watchId , parser );
213
283
} else if (ThrottlerField .THROTTLE_PERIOD .match (currentFieldName , parser .getDeprecationHandler ())) {
@@ -235,7 +305,7 @@ static ActionWrapper parse(String watchId, String actionId, XContentParser parse
235
305
}
236
306
237
307
ActionThrottler throttler = new ActionThrottler (clock , throttlePeriod , licenseState );
238
- return new ActionWrapper (actionId , throttler , condition , transform , action );
308
+ return new ActionWrapper (actionId , throttler , condition , transform , action , path );
239
309
}
240
310
241
311
}
0 commit comments