diff --git a/graylog-project-parent/pom.xml b/graylog-project-parent/pom.xml
index 15c2e0a586e3..42ed068e958f 100644
--- a/graylog-project-parent/pom.xml
+++ b/graylog-project-parent/pom.xml
@@ -500,6 +500,21 @@
cef-parser${cef-parser.version}
+
+ org.antlr
+ antlr4-runtime
+ ${antlr.version}
+
+
+ org.jooq
+ jool
+ ${jool.version}
+
+
+ com.squareup
+ javapoet
+ ${javapoet.version}
+
diff --git a/graylog2-server/pom.xml b/graylog2-server/pom.xml
index a70602b1866d..ae27c95e3705 100644
--- a/graylog2-server/pom.xml
+++ b/graylog2-server/pom.xml
@@ -489,6 +489,18 @@
org.graylog.cefcef-parser
+
+ org.antlr
+ antlr4-runtime
+
+
+ org.jooq
+ jool
+
+
+ com.squareup
+ javapoet
+ nl.jqno.equalsverifier
@@ -690,6 +702,18 @@
com.mycilalicense-maven-plugin
+
+ org.antlr
+ antlr4-maven-plugin
+
+
+ antlr
+
+ antlr4
+
+
+
+
diff --git a/graylog2-server/src/main/antlr4/org/graylog/plugins/pipelineprocessor/parser/RuleLang.g4 b/graylog2-server/src/main/antlr4/org/graylog/plugins/pipelineprocessor/parser/RuleLang.g4
new file mode 100644
index 000000000000..cb20c6717f6c
--- /dev/null
+++ b/graylog2-server/src/main/antlr4/org/graylog/plugins/pipelineprocessor/parser/RuleLang.g4
@@ -0,0 +1,435 @@
+/*
+ Parts of the grammar are derived from the Java.g4 grammar at https://github.com/antlr/grammars-v4/blob/master/java/Java.g4
+ Those parts are under the following license:
+
+ [The "BSD licence"]
+ Copyright (c) 2013 Terence Parr, Sam Harwell
+ All rights reserved.
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+grammar RuleLang;
+
+@header {
+import org.graylog.plugins.pipelineprocessor.ast.expressions.Expression;
+}
+
+file
+ : ( ruleDeclaration
+ | pipelineDeclaration
+ )+
+ EOF
+ ;
+
+pipelineDecls
+ : pipelineDeclaration+ EOF
+ ;
+
+pipeline
+ : pipelineDeclaration EOF
+ ;
+
+pipelineDeclaration
+ : Pipeline name=String
+ stageDeclaration+
+ End
+ ;
+
+stageDeclaration
+ : Stage stage=Integer Match modifier=(All|Either)
+ ruleRef*
+ ;
+
+ruleRef
+ : Rule name=String ';'?
+ ;
+
+ruleDecls
+ : ruleDeclaration+ EOF
+ ;
+
+ruleDeclaration
+ : Rule name=String
+ When condition=expression
+ (Then actions=statement*)?
+ End
+ ;
+
+expression
+ : '(' expression ')' # ParenExpr
+ | literal # LiteralPrimary
+ | functionCall # Func
+ | Identifier # Identifier
+ | '[' (expression (',' expression)*)* ']' # ArrayLiteralExpr
+ | '{' (propAssignment (',' propAssignment)*)* '}' # MapLiteralExpr
+ | MessageRef '.' field=expression # MessageRef
+ | fieldSet=expression '.' field=expression # Nested
+ | array=expression '[' index=expression ']' # IndexedAccess
+ | sign=('+'|'-') expr=expression # SignedExpression
+ | Not expression # Not
+ | left=expression mult=('*'|'/'|'%') right=expression # Multiplication
+ | left=expression add=('+'|'-') right=expression # Addition
+ | left=expression comparison=('<=' | '>=' | '>' | '<') right=expression # Comparison
+ | left=expression equality=('==' | '!=') right=expression # Equality
+ | left=expression and=And right=expression # And
+ | left=expression or=Or right=expression # Or
+ ;
+
+propAssignment
+ : Identifier ':' expression
+ ;
+
+statement
+ : functionCall ';' # FuncStmt
+ | Let varName=Identifier '=' expression ';' # VarAssignStmt
+ | ';' # EmptyStmt
+ ;
+
+functionCall
+ : funcName=Identifier '(' arguments? ')'
+ ;
+
+arguments
+ : propAssignment (',' propAssignment)* # NamedArgs
+ | expression (',' expression)* # PositionalArgs
+ ;
+
+literal
+ : Integer # Integer
+ | Float # Float
+ | Char # Char
+ | String # String
+ | Boolean # Boolean
+ ;
+
+// Lexer
+
+All : A L L;
+Either: E I T H E R;
+And : A N D | '&&';
+Or: O R | '||';
+Not: N O T | '!';
+Pipeline: P I P E L I N E;
+Rule: R U L E;
+During: D U R I N G;
+Stage: S T A G E;
+When: W H E N;
+Then: T H E N;
+End: E N D;
+Let: L E T;
+Match: M A T C H;
+MessageRef: '$message';
+
+Boolean
+ : 'true'|'false'
+ ;
+
+// Integer literals
+
+Integer
+ : DecimalIntegerLiteral
+ | HexIntegerLiteral
+ | OctalIntegerLiteral
+ | BinaryIntegerLiteral
+ ;
+
+fragment
+DecimalIntegerLiteral
+ : Sign? DecimalNumeral IntegerTypeSuffix?
+ ;
+
+fragment
+HexIntegerLiteral
+ : Sign? HexNumeral IntegerTypeSuffix?
+ ;
+
+fragment
+OctalIntegerLiteral
+ : Sign? OctalNumeral IntegerTypeSuffix?
+ ;
+
+fragment
+BinaryIntegerLiteral
+ : Sign? BinaryNumeral IntegerTypeSuffix?
+ ;
+
+fragment
+IntegerTypeSuffix
+ : [lL]
+ ;
+
+fragment
+DecimalNumeral
+ : '0'
+ | NonZeroDigit (Digits? | Underscores Digits)
+ ;
+
+fragment
+Digits
+ : Digit (DigitOrUnderscore* Digit)?
+ ;
+
+fragment
+Digit
+ : '0'
+ | NonZeroDigit
+ ;
+
+fragment
+NonZeroDigit
+ : [1-9]
+ ;
+
+fragment
+DigitOrUnderscore
+ : Digit
+ | '_'
+ ;
+
+fragment
+Underscores
+ : '_'+
+ ;
+
+fragment
+HexNumeral
+ : '0' [xX] HexDigits
+ ;
+
+fragment
+HexDigits
+ : HexDigit (HexDigitOrUnderscore* HexDigit)?
+ ;
+
+fragment
+HexDigit
+ : [0-9a-fA-F]
+ ;
+
+fragment
+HexDigitOrUnderscore
+ : HexDigit
+ | '_'
+ ;
+
+fragment
+OctalNumeral
+ : '0' Underscores? OctalDigits
+ ;
+
+fragment
+OctalDigits
+ : OctalDigit (OctalDigitOrUnderscore* OctalDigit)?
+ ;
+
+fragment
+OctalDigit
+ : [0-7]
+ ;
+
+fragment
+OctalDigitOrUnderscore
+ : OctalDigit
+ | '_'
+ ;
+
+fragment
+BinaryNumeral
+ : '0' [bB] BinaryDigits
+ ;
+
+fragment
+BinaryDigits
+ : BinaryDigit (BinaryDigitOrUnderscore* BinaryDigit)?
+ ;
+
+fragment
+BinaryDigit
+ : [01]
+ ;
+
+fragment
+BinaryDigitOrUnderscore
+ : BinaryDigit
+ | '_'
+ ;
+
+// Floats
+Float
+ : Sign? DecimalFloatingPointLiteral
+ | Sign? HexadecimalFloatingPointLiteral
+ ;
+
+fragment
+DecimalFloatingPointLiteral
+ : Digits '.' Digits? ExponentPart? FloatTypeSuffix?
+ | '.' Digits ExponentPart? FloatTypeSuffix?
+ | Digits ExponentPart FloatTypeSuffix?
+ | Digits FloatTypeSuffix
+ ;
+
+fragment
+ExponentPart
+ : ExponentIndicator SignedInteger
+ ;
+
+fragment
+ExponentIndicator
+ : [eE]
+ ;
+
+fragment
+SignedInteger
+ : Sign? Digits
+ ;
+
+fragment
+Sign
+ : [+-]
+ ;
+
+fragment
+FloatTypeSuffix
+ : [fFdD]
+ ;
+
+fragment
+HexadecimalFloatingPointLiteral
+ : HexSignificand BinaryExponent FloatTypeSuffix?
+ ;
+
+fragment
+HexSignificand
+ : HexNumeral '.'?
+ | '0' [xX] HexDigits? '.' HexDigits
+ ;
+
+fragment
+BinaryExponent
+ : BinaryExponentIndicator SignedInteger
+ ;
+
+fragment
+BinaryExponentIndicator
+ : [pP]
+ ;
+
+// Char
+
+Char
+ : '\'' SingleCharacter '\''
+ | '\'' EscapeSequence '\''
+ ;
+
+fragment
+SingleCharacter
+ : ~['\\]
+ ;
+
+// String literals
+String
+ : '"' StringCharacters? '"'
+ ;
+fragment
+StringCharacters
+ : StringCharacter+
+ ;
+fragment
+StringCharacter
+ : ~["\\]
+ | EscapeSequence
+ ;
+// ยง3.10.6 Escape Sequences for Character and String Literals
+fragment
+EscapeSequence
+ : '\\' [btnfr"'\\]
+ | OctalEscape
+ | UnicodeEscape
+ ;
+
+fragment
+OctalEscape
+ : '\\' OctalDigit
+ | '\\' OctalDigit OctalDigit
+ | '\\' ZeroToThree OctalDigit OctalDigit
+ ;
+
+fragment
+UnicodeEscape
+ : '\\' 'u' HexDigit HexDigit HexDigit HexDigit
+ ;
+
+fragment
+ZeroToThree
+ : [0-3]
+ ;
+
+Identifier
+ : [a-zA-Z_] [a-zA-Z_0-9]*
+ | '`' ~['`']+ '`'
+ ;
+
+
+//
+// Whitespace and comments
+//
+
+WS : [ \t\r\n\u000C]+ -> skip
+ ;
+
+COMMENT
+ : '/*' .*? '*/' -> skip
+ ;
+
+LINE_COMMENT
+ : '//' ~[\r\n]* -> skip
+ ;
+
+
+// to support case insensitive keywords
+
+fragment A : [aA];
+fragment B : [bB];
+fragment C : [cC];
+fragment D : [dD];
+fragment E : [eE];
+fragment F : [fF];
+fragment G : [gG];
+fragment H : [hH];
+fragment I : [iI];
+fragment J : [jJ];
+fragment K : [kK];
+fragment L : [lL];
+fragment M : [mM];
+fragment N : [nN];
+fragment O : [oO];
+fragment P : [pP];
+fragment Q : [qQ];
+fragment R : [rR];
+fragment S : [sS];
+fragment T : [tT];
+fragment U : [uU];
+fragment V : [vV];
+fragment W : [wW];
+fragment X : [xX];
+fragment Y : [yY];
+fragment Z : [zZ];
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/EvaluationContext.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/EvaluationContext.java
new file mode 100644
index 000000000000..d24dbd3a18ae
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/EvaluationContext.java
@@ -0,0 +1,170 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.graylog.plugins.pipelineprocessor.ast.functions.FunctionDescriptor;
+import org.graylog2.plugin.EmptyMessages;
+import org.graylog2.plugin.Message;
+import org.graylog2.plugin.MessageCollection;
+import org.graylog2.plugin.Messages;
+import org.joda.time.DateTime;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+public class EvaluationContext {
+
+ private static final EvaluationContext EMPTY_CONTEXT = new EvaluationContext() {
+ @Override
+ public void addCreatedMessage(Message newMessage) {
+ // cannot add messages to empty context
+ }
+
+ @Override
+ public void define(String identifier, Class type, Object value) {
+ // cannot define any variables in empty context
+ }
+ };
+
+ @Nonnull
+ private final Message message;
+ @Nullable
+ private Map ruleVars;
+ @Nullable
+ private List createdMessages;
+ @Nullable
+ private List evalErrors;
+
+ private EvaluationContext() {
+ this(new Message("__dummy", "__dummy", DateTime.parse("2010-07-30T16:03:25Z"))); // first Graylog release
+ }
+
+ public EvaluationContext(@Nonnull Message message) {
+ this.message = message;
+ }
+
+ public void define(String identifier, Class type, Object value) {
+ if (ruleVars == null) {
+ ruleVars = Maps.newHashMap();
+ }
+ ruleVars.put(identifier, new TypedValue(type, value));
+ }
+
+ public Message currentMessage() {
+ return message;
+ }
+
+ public TypedValue get(String identifier) {
+ if (ruleVars == null) {
+ throw new IllegalStateException("Use of undeclared variable " + identifier);
+ }
+ return ruleVars.get(identifier);
+ }
+
+ public Messages createdMessages() {
+ if (createdMessages == null) {
+ return new EmptyMessages();
+ }
+ return new MessageCollection(createdMessages);
+ }
+
+ public void addCreatedMessage(Message newMessage) {
+ if (createdMessages == null) {
+ createdMessages = Lists.newArrayList();
+ }
+ createdMessages.add(newMessage);
+ }
+
+ public void clearCreatedMessages() {
+ if (createdMessages != null) {
+ createdMessages.clear();
+ }
+ }
+
+ public static EvaluationContext emptyContext() {
+ return EMPTY_CONTEXT;
+ }
+
+ public void addEvaluationError(int line, int charPositionInLine, @Nullable FunctionDescriptor descriptor, Throwable e) {
+ if (evalErrors == null) {
+ evalErrors = Lists.newArrayList();
+ }
+ evalErrors.add(new EvalError(line, charPositionInLine, descriptor, e));
+ }
+
+ public boolean hasEvaluationErrors() {
+ return evalErrors != null;
+ }
+
+ public List evaluationErrors() {
+ return evalErrors == null ? Collections.emptyList() : Collections.unmodifiableList(evalErrors);
+ }
+
+ public class TypedValue {
+ private final Class type;
+ private final Object value;
+
+ public TypedValue(Class type, Object value) {
+ this.type = type;
+ this.value = value;
+ }
+
+ public Class getType() {
+ return type;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+ }
+
+ public static class EvalError {
+ private final int line;
+ private final int charPositionInLine;
+ @Nullable
+ private final FunctionDescriptor descriptor;
+ private final Throwable throwable;
+
+ public EvalError(int line, int charPositionInLine, @Nullable FunctionDescriptor descriptor, Throwable throwable) {
+ this.line = line;
+ this.charPositionInLine = charPositionInLine;
+ this.descriptor = descriptor;
+ this.throwable = throwable;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ if (descriptor != null) {
+ sb.append("In call to function '").append(descriptor.name()).append("' at ");
+ } else {
+ sb.append("At ");
+ }
+ return sb.append(line)
+ .append(":")
+ .append(charPositionInLine)
+ .append(" an exception was thrown: ")
+ .append(throwable.getMessage())
+ .toString();
+ }
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/PipelineConfig.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/PipelineConfig.java
new file mode 100644
index 000000000000..da177687c210
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/PipelineConfig.java
@@ -0,0 +1,30 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor;
+
+import com.github.joschi.jadconfig.Parameter;
+
+import org.graylog2.plugin.PluginConfigBean;
+
+public class PipelineConfig implements PluginConfigBean {
+
+ @Parameter("cached_stageiterators")
+ private boolean cachedStageIterators = true;
+
+ @Parameter("generate_native_code")
+ private boolean generateNativeCode = false;
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/PipelineProcessorMessageDecorator.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/PipelineProcessorMessageDecorator.java
new file mode 100644
index 000000000000..67e0405d2823
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/PipelineProcessorMessageDecorator.java
@@ -0,0 +1,133 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.assistedinject.Assisted;
+
+import org.graylog.plugins.pipelineprocessor.db.PipelineDao;
+import org.graylog.plugins.pipelineprocessor.db.PipelineService;
+import org.graylog.plugins.pipelineprocessor.processors.ConfigurationStateUpdater;
+import org.graylog.plugins.pipelineprocessor.processors.PipelineInterpreter;
+import org.graylog.plugins.pipelineprocessor.processors.listeners.NoopInterpreterListener;
+import org.graylog2.decorators.Decorator;
+import org.graylog2.plugin.Message;
+import org.graylog2.plugin.configuration.ConfigurationRequest;
+import org.graylog2.plugin.configuration.fields.ConfigurationField;
+import org.graylog2.plugin.configuration.fields.DropdownField;
+import org.graylog2.plugin.decorators.SearchResponseDecorator;
+import org.graylog2.rest.models.messages.responses.ResultMessageSummary;
+import org.graylog2.rest.resources.search.responses.SearchResponse;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+
+public class PipelineProcessorMessageDecorator implements SearchResponseDecorator {
+ private static final String CONFIG_FIELD_PIPELINE = "pipeline";
+
+ private final PipelineInterpreter pipelineInterpreter;
+ private final ConfigurationStateUpdater pipelineStateUpdater;
+ private final ImmutableSet pipelines;
+
+ public interface Factory extends SearchResponseDecorator.Factory {
+ @Override
+ PipelineProcessorMessageDecorator create(Decorator decorator);
+
+ @Override
+ Config getConfig();
+
+ @Override
+ Descriptor getDescriptor();
+ }
+
+ public static class Config implements SearchResponseDecorator.Config {
+ private final PipelineService pipelineService;
+
+ @Inject
+ public Config(PipelineService pipelineService) {
+ this.pipelineService = pipelineService;
+ }
+
+ @Override
+ public ConfigurationRequest getRequestedConfiguration() {
+ final Map pipelineOptions = this.pipelineService.loadAll().stream()
+ .sorted((o1, o2) -> o1.title().compareTo(o2.title()))
+ .collect(Collectors.toMap(PipelineDao::id, PipelineDao::title));
+ return new ConfigurationRequest() {{
+ addField(new DropdownField(CONFIG_FIELD_PIPELINE,
+ "Pipeline",
+ "",
+ pipelineOptions,
+ "Which pipeline to use for message decoration",
+ ConfigurationField.Optional.NOT_OPTIONAL));
+ }};
+ };
+ }
+
+ public static class Descriptor extends SearchResponseDecorator.Descriptor {
+ public Descriptor() {
+ super("Pipeline Processor Decorator", "http://docs.graylog.org/en/2.0/pages/pipelines.html", "Pipeline Processor Decorator");
+ }
+ }
+
+ @Inject
+ public PipelineProcessorMessageDecorator(PipelineInterpreter pipelineInterpreter,
+ ConfigurationStateUpdater pipelineStateUpdater,
+ @Assisted Decorator decorator) {
+ this.pipelineInterpreter = pipelineInterpreter;
+ this.pipelineStateUpdater = pipelineStateUpdater;
+ final String pipelineId = (String)decorator.config().get(CONFIG_FIELD_PIPELINE);
+ if (Strings.isNullOrEmpty(pipelineId)) {
+ this.pipelines = ImmutableSet.of();
+ } else {
+ this.pipelines = ImmutableSet.of(pipelineId);
+ }
+ }
+
+ @Override
+ public SearchResponse apply(SearchResponse searchResponse) {
+ final List results = new ArrayList<>();
+ if (pipelines.isEmpty()) {
+ return searchResponse;
+ }
+ searchResponse.messages().forEach((inMessage) -> {
+ final Message message = new Message(inMessage.message());
+ final List additionalCreatedMessages = pipelineInterpreter.processForPipelines(message,
+ pipelines,
+ new NoopInterpreterListener(),
+ pipelineStateUpdater.getLatestState());
+
+ results.add(ResultMessageSummary.create(inMessage.highlightRanges(), message.getFields(), inMessage.index()));
+ additionalCreatedMessages.forEach((additionalMessage) -> {
+ // TODO: pass proper highlight ranges. Need to rebuild them for new messages.
+ results.add(ResultMessageSummary.create(
+ ImmutableMultimap.of(),
+ additionalMessage.getFields(),
+ "[created from decorator]"
+ ));
+ });
+ });
+
+ return searchResponse.toBuilder().messages(results).build();
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/PipelineProcessorModule.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/PipelineProcessorModule.java
new file mode 100644
index 000000000000..38bbdcd52cea
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/PipelineProcessorModule.java
@@ -0,0 +1,58 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor;
+
+import com.google.inject.assistedinject.FactoryModuleBuilder;
+
+import org.graylog.plugins.pipelineprocessor.audit.PipelineProcessorAuditEventTypes;
+import org.graylog.plugins.pipelineprocessor.functions.ProcessorFunctionsModule;
+import org.graylog.plugins.pipelineprocessor.periodical.LegacyDefaultStreamMigration;
+import org.graylog.plugins.pipelineprocessor.processors.PipelineInterpreter;
+import org.graylog.plugins.pipelineprocessor.rest.PipelineConnectionsResource;
+import org.graylog.plugins.pipelineprocessor.rest.PipelineResource;
+import org.graylog.plugins.pipelineprocessor.rest.PipelineRestPermissions;
+import org.graylog.plugins.pipelineprocessor.rest.RuleResource;
+import org.graylog.plugins.pipelineprocessor.rest.SimulatorResource;
+import org.graylog2.plugin.PluginConfigBean;
+import org.graylog2.plugin.PluginModule;
+
+import java.util.Collections;
+import java.util.Set;
+
+public class PipelineProcessorModule extends PluginModule {
+ @Override
+ protected void configure() {
+ addPeriodical(LegacyDefaultStreamMigration.class);
+
+ addMessageProcessor(PipelineInterpreter.class, PipelineInterpreter.Descriptor.class);
+ addRestResource(RuleResource.class);
+ addRestResource(PipelineResource.class);
+ addRestResource(PipelineConnectionsResource.class);
+ addRestResource(SimulatorResource.class);
+ addPermissions(PipelineRestPermissions.class);
+
+ install(new ProcessorFunctionsModule());
+
+ installSearchResponseDecorator(searchResponseDecoratorBinder(),
+ PipelineProcessorMessageDecorator.class,
+ PipelineProcessorMessageDecorator.Factory.class);
+
+ install(new FactoryModuleBuilder().build(PipelineInterpreter.State.Factory.class));
+
+ addAuditEventTypes(PipelineProcessorAuditEventTypes.class);
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/Pipeline.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/Pipeline.java
new file mode 100644
index 000000000000..8531475ecdbd
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/Pipeline.java
@@ -0,0 +1,106 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast;
+
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricFilter;
+import com.codahale.metrics.MetricRegistry;
+import com.google.auto.value.AutoValue;
+import com.google.auto.value.extension.memoized.Memoized;
+import com.google.common.collect.Sets;
+import org.graylog2.shared.metrics.MetricUtils;
+
+import javax.annotation.Nullable;
+import java.util.SortedSet;
+
+@AutoValue
+public abstract class Pipeline {
+
+ private String metricName;
+ private transient Meter executed;
+
+ @Nullable
+ public abstract String id();
+ public abstract String name();
+ public abstract SortedSet stages();
+
+ public static Builder builder() {
+ return new AutoValue_Pipeline.Builder();
+ }
+
+ public static Pipeline empty(String name) {
+ return builder().name(name).stages(Sets.newTreeSet()).build();
+ }
+
+ public abstract Builder toBuilder();
+
+ public Pipeline withId(String id) {
+ return toBuilder().id(id).build();
+ }
+
+ @Memoized
+ public abstract int hashCode();
+
+ /**
+ * Register the metrics attached to this pipeline.
+ *
+ * @param metricRegistry the registry to add the metrics to
+ */
+ public void registerMetrics(MetricRegistry metricRegistry) {
+ if (id() != null) {
+ metricName = MetricRegistry.name(Pipeline.class, id(), "executed");
+ executed = metricRegistry.meter(metricName);
+ }
+ }
+
+ /**
+ * The metric filter matching all metrics that have been registered by this pipeline.
+ * Commonly used to remove the relevant metrics from the registry upon deletion of the pipeline.
+ *
+ * @return the filter matching this pipeline's metrics
+ */
+ public MetricFilter metricsFilter() {
+ if (id() == null) {
+ return (name, metric) -> false;
+ }
+ return new MetricUtils.SingleMetricFilter(metricName);
+
+ }
+ public void markExecution() {
+ if (executed != null) {
+ executed.mark();
+ }
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Pipeline build();
+
+ public abstract Builder id(String id);
+
+ public abstract Builder name(String name);
+
+ public abstract Builder stages(SortedSet stages);
+ }
+
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("Pipeline ");
+ sb.append("'").append(name()).append("'");
+ sb.append(" (").append(id()).append(")");
+ return sb.toString();
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/Rule.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/Rule.java
new file mode 100644
index 000000000000..714a504d7b3e
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/Rule.java
@@ -0,0 +1,218 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricFilter;
+import com.codahale.metrics.MetricRegistry;
+
+import org.antlr.v4.runtime.CommonToken;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.BooleanExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.LogicalExpression;
+import org.graylog.plugins.pipelineprocessor.ast.statements.Statement;
+import org.graylog.plugins.pipelineprocessor.codegen.GeneratedRule;
+import org.graylog.plugins.pipelineprocessor.parser.FunctionRegistry;
+import org.reflections.ReflectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+@AutoValue
+public abstract class Rule {
+ private static final Logger LOG = LoggerFactory.getLogger(Rule.class);
+
+ private transient Set metricNames = Sets.newHashSet();
+
+ private transient Meter globalExecuted;
+ private transient Meter localExecuted;
+ private transient Meter globalFailed;
+ private transient Meter localFailed;
+ private transient Meter globalMatched;
+ private transient Meter localMatched;
+ private transient Meter globalNotMatched;
+ private transient Meter localNotMatched;
+
+ @Nullable
+ public abstract String id();
+
+ public abstract String name();
+
+ public abstract LogicalExpression when();
+
+ public abstract Collection then();
+
+ @Nullable
+ public abstract Class extends GeneratedRule> generatedRuleClass();
+
+ @Nullable
+ public abstract GeneratedRule generatedRule();
+
+ public static Builder builder() {
+ return new AutoValue_Rule.Builder();
+ }
+
+ public abstract Builder toBuilder();
+
+ public Rule withId(String id) {
+ return toBuilder().id(id).build();
+ }
+
+ public static Rule alwaysFalse(String name) {
+ return builder().name(name).when(new BooleanExpression(new CommonToken(-1), false)).then(Collections.emptyList()).build();
+ }
+
+ /**
+ * Register the metrics attached to this pipeline.
+ *
+ * @param metricRegistry the registry to add the metrics to
+ */
+ public void registerMetrics(MetricRegistry metricRegistry, String pipelineId, String stageId) {
+ if (id() == null) {
+ LOG.debug("Not registering metrics for unsaved rule {}", name());
+ return;
+ }
+ if (id() != null) {
+ globalExecuted = registerGlobalMeter(metricRegistry, "executed");
+ localExecuted = registerLocalMeter(metricRegistry, pipelineId, stageId, "executed");
+
+ globalFailed = registerGlobalMeter(metricRegistry, "failed");
+ localFailed = registerLocalMeter(metricRegistry, pipelineId, stageId, "failed");
+
+ globalMatched = registerGlobalMeter(metricRegistry, "matched");
+ localMatched = registerLocalMeter(metricRegistry, pipelineId, stageId, "matched");
+
+ globalNotMatched = registerGlobalMeter(metricRegistry, "not-matched");
+ localNotMatched = registerLocalMeter(metricRegistry, pipelineId, stageId, "not-matched");
+
+ }
+ }
+
+ private Meter registerGlobalMeter(MetricRegistry metricRegistry, String type) {
+ final String name = MetricRegistry.name(Rule.class, id(), type);
+ metricNames.add(name);
+ return metricRegistry.meter(name);
+ }
+
+ private Meter registerLocalMeter(MetricRegistry metricRegistry,
+ String pipelineId,
+ String stageId, String type) {
+ final String name = MetricRegistry.name(Rule.class, id(), pipelineId, stageId, type);
+ metricNames.add(name);
+ return metricRegistry.meter(name);
+ }
+
+ /**
+ * The metric filter matching all metrics that have been registered by this pipeline.
+ * Commonly used to remove the relevant metrics from the registry upon deletion of the pipeline.
+ *
+ * @return the filter matching this pipeline's metrics
+ */
+ public MetricFilter metricsFilter() {
+ if (id() == null) {
+ return (name, metric) -> false;
+ }
+ return (name, metric) -> metricNames.contains(name);
+
+ }
+
+ public void markExecution() {
+ if (id() != null) {
+ globalExecuted.mark();
+ localExecuted.mark();
+ }
+ }
+
+ public void markMatch() {
+ if (id() != null) {
+ globalMatched.mark();
+ localMatched.mark();
+ }
+ }
+
+ public void markNonMatch() {
+ if (id() != null) {
+ globalNotMatched.mark();
+ localNotMatched.mark();
+ }
+ }
+
+ public void markFailure() {
+ if (id() != null) {
+ globalFailed.mark();
+ localFailed.mark();
+ }
+ }
+
+ /**
+ * Creates a copy of this Rule with a new instance of the generated rule class if present.
+ *
+ * This prevents sharing instances across threads, which is not supported for performance reasons.
+ * Otherwise the generated code would need to be thread safe, adding to the runtime overhead.
+ * Instead we buy speed by spending more memory.
+ *
+ * @param functionRegistry the registered functions of the system
+ * @return a copy of this rule with a new instance of its generated code
+ */
+ public Rule invokableCopy(FunctionRegistry functionRegistry) {
+ final Builder builder = toBuilder();
+ final Class extends GeneratedRule> ruleClass = generatedRuleClass();
+ if (ruleClass != null) {
+ try {
+ //noinspection unchecked
+ final Set constructors = ReflectionUtils.getConstructors(ruleClass);
+ final Constructor onlyElement = Iterables.getOnlyElement(constructors);
+ final GeneratedRule instance = (GeneratedRule) onlyElement.newInstance(functionRegistry);
+ builder.generatedRule(instance);
+ } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
+ LOG.warn("Unable to generate code for rule {}: {}", id(), e);
+ }
+ }
+ return builder.build();
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+
+ public abstract Builder id(String id);
+ public abstract Builder name(String name);
+ public abstract Builder when(LogicalExpression condition);
+ public abstract Builder then(Collection actions);
+ public abstract Builder generatedRuleClass(@Nullable Class extends GeneratedRule> klass);
+ public abstract Builder generatedRule(GeneratedRule instance);
+
+ public abstract Rule build();
+ }
+
+
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("Rule ");
+ sb.append("'").append(name()).append("'");
+ sb.append(" (").append(id()).append(")");
+ return sb.toString();
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/RuleAstBaseListener.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/RuleAstBaseListener.java
new file mode 100644
index 000000000000..5c55d4f22322
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/RuleAstBaseListener.java
@@ -0,0 +1,390 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast;
+
+import org.graylog.plugins.pipelineprocessor.ast.expressions.AdditionExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.AndExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.ArrayLiteralExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.BinaryExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.BooleanExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.BooleanValuedFunctionWrapper;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.ComparisonExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.ConstantExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.DoubleExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.EqualityExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.Expression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.FieldAccessExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.FieldRefExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.FunctionExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.IndexedAccessExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.LogicalExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.LongExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.MapLiteralExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.MessageRefExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.MultiplicationExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.NotExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.NumericExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.OrExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.SignedExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.StringExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.UnaryExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.VarRefExpression;
+import org.graylog.plugins.pipelineprocessor.ast.statements.FunctionStatement;
+import org.graylog.plugins.pipelineprocessor.ast.statements.Statement;
+import org.graylog.plugins.pipelineprocessor.ast.statements.VarAssignStatement;
+
+public class RuleAstBaseListener implements RuleAstListener {
+ @Override
+ public void enterRule(Rule rule) {
+
+ }
+
+ @Override
+ public void exitRule(Rule rule) {
+
+ }
+
+ @Override
+ public void enterWhen(Rule rule) {
+
+ }
+
+ @Override
+ public void exitWhen(Rule rule) {
+
+ }
+
+ @Override
+ public void enterThen(Rule rule) {
+
+ }
+
+ @Override
+ public void exitThen(Rule rule) {
+
+ }
+
+ @Override
+ public void enterStatement(Statement statement) {
+
+ }
+
+ @Override
+ public void exitStatement(Statement statement) {
+
+ }
+
+ @Override
+ public void enterFunctionCallStatement(FunctionStatement func) {
+
+ }
+
+ @Override
+ public void exitFunctionCallStatement(FunctionStatement func) {
+
+ }
+
+ @Override
+ public void enterVariableAssignStatement(VarAssignStatement assign) {
+
+ }
+
+ @Override
+ public void exitVariableAssignStatement(VarAssignStatement assign) {
+
+ }
+
+ @Override
+ public void enterAddition(AdditionExpression expr) {
+
+ }
+
+ @Override
+ public void exitAddition(AdditionExpression expr) {
+
+ }
+
+ @Override
+ public void enterAnd(AndExpression expr) {
+
+ }
+
+ @Override
+ public void exitAnd(AndExpression expr) {
+
+ }
+
+ @Override
+ public void enterArrayLiteral(ArrayLiteralExpression expr) {
+
+ }
+
+ @Override
+ public void exitArrayLiteral(ArrayLiteralExpression expr) {
+
+ }
+
+ @Override
+ public void enterBinary(BinaryExpression expr) {
+
+ }
+
+ @Override
+ public void exitBinary(BinaryExpression expr) {
+
+ }
+
+ @Override
+ public void enterBoolean(BooleanExpression expr) {
+
+ }
+
+ @Override
+ public void exitBoolean(BooleanExpression expr) {
+
+ }
+
+ @Override
+ public void enterBooleanFuncWrapper(BooleanValuedFunctionWrapper expr) {
+
+ }
+
+ @Override
+ public void exitBooleanFuncWrapper(BooleanValuedFunctionWrapper expr) {
+
+ }
+
+ @Override
+ public void enterComparison(ComparisonExpression expr) {
+
+ }
+
+ @Override
+ public void exitComparison(ComparisonExpression expr) {
+
+ }
+
+ @Override
+ public void enterConstant(ConstantExpression expr) {
+
+ }
+
+ @Override
+ public void exitConstant(ConstantExpression expr) {
+
+ }
+
+ @Override
+ public void enterDouble(DoubleExpression expr) {
+
+ }
+
+ @Override
+ public void exitDouble(DoubleExpression expr) {
+
+ }
+
+ @Override
+ public void enterEquality(EqualityExpression expr) {
+
+ }
+
+ @Override
+ public void exitEquality(EqualityExpression expr) {
+
+ }
+
+ @Override
+ public void enterFieldAccess(FieldAccessExpression expr) {
+
+ }
+
+ @Override
+ public void exitFieldAccess(FieldAccessExpression expr) {
+
+ }
+
+ @Override
+ public void enterFieldRef(FieldRefExpression expr) {
+
+ }
+
+ @Override
+ public void exitFieldRef(FieldRefExpression expr) {
+
+ }
+
+ @Override
+ public void enterFunctionCall(FunctionExpression expr) {
+
+ }
+
+ @Override
+ public void exitFunctionCall(FunctionExpression expr) {
+
+ }
+
+ @Override
+ public void enterIndexedAccess(IndexedAccessExpression expr) {
+
+ }
+
+ @Override
+ public void exitIndexedAccess(IndexedAccessExpression expr) {
+
+ }
+
+ @Override
+ public void enterLogical(LogicalExpression expr) {
+
+ }
+
+ @Override
+ public void exitLogical(LogicalExpression expr) {
+
+ }
+
+ @Override
+ public void enterLong(LongExpression expr) {
+
+ }
+
+ @Override
+ public void exitLong(LongExpression expr) {
+
+ }
+
+ @Override
+ public void enterMapLiteral(MapLiteralExpression expr) {
+
+ }
+
+ @Override
+ public void exitMapLiteral(MapLiteralExpression expr) {
+
+ }
+
+ @Override
+ public void enterMessageRef(MessageRefExpression expr) {
+
+ }
+
+ @Override
+ public void exitMessageRef(MessageRefExpression expr) {
+
+ }
+
+ @Override
+ public void enterMultiplication(MultiplicationExpression expr) {
+
+ }
+
+ @Override
+ public void exitMultiplication(MultiplicationExpression expr) {
+
+ }
+
+ @Override
+ public void enterNot(NotExpression expr) {
+
+ }
+
+ @Override
+ public void exitNot(NotExpression expr) {
+
+ }
+
+ @Override
+ public void enterNumeric(NumericExpression expr) {
+
+ }
+
+ @Override
+ public void exitNumeric(NumericExpression expr) {
+
+ }
+
+ @Override
+ public void enterOr(OrExpression expr) {
+
+ }
+
+ @Override
+ public void exitOr(OrExpression expr) {
+
+ }
+
+ @Override
+ public void enterSigned(SignedExpression expr) {
+
+ }
+
+ @Override
+ public void exitSigned(SignedExpression expr) {
+
+ }
+
+ @Override
+ public void enterString(StringExpression expr) {
+
+ }
+
+ @Override
+ public void exitString(StringExpression expr) {
+
+ }
+
+ @Override
+ public void enterUnary(UnaryExpression expr) {
+
+ }
+
+ @Override
+ public void exitUnary(UnaryExpression expr) {
+
+ }
+
+ @Override
+ public void enterVariableReference(VarRefExpression expr) {
+
+ }
+
+ @Override
+ public void exitVariableReference(VarRefExpression expr) {
+
+ }
+
+ @Override
+ public void enterEveryExpression(Expression expr) {
+
+ }
+
+ @Override
+ public void exitEveryExpression(Expression expr) {
+
+ }
+
+ @Override
+ public void enterFunctionArg(FunctionExpression functionExpression, Expression expression) {
+
+ }
+
+ @Override
+ public void exitFunctionArg(Expression expression) {
+
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/RuleAstListener.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/RuleAstListener.java
new file mode 100644
index 000000000000..fde3d8b67deb
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/RuleAstListener.java
@@ -0,0 +1,189 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast;
+
+import org.graylog.plugins.pipelineprocessor.ast.expressions.AdditionExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.AndExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.ArrayLiteralExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.BinaryExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.BooleanExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.BooleanValuedFunctionWrapper;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.ComparisonExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.ConstantExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.DoubleExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.EqualityExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.Expression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.FieldAccessExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.FieldRefExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.FunctionExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.IndexedAccessExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.LogicalExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.LongExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.MapLiteralExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.MessageRefExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.MultiplicationExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.NotExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.NumericExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.OrExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.SignedExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.StringExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.UnaryExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.VarRefExpression;
+import org.graylog.plugins.pipelineprocessor.ast.statements.FunctionStatement;
+import org.graylog.plugins.pipelineprocessor.ast.statements.Statement;
+import org.graylog.plugins.pipelineprocessor.ast.statements.VarAssignStatement;
+
+/**
+ * Consider using RuleAstBaseListener to only implement the callbacks relevant to you.
+ */
+public interface RuleAstListener {
+ void enterRule(Rule rule);
+
+ void exitRule(Rule rule);
+
+ void enterWhen(Rule rule);
+
+ void exitWhen(Rule rule);
+
+ void enterThen(Rule rule);
+
+ void exitThen(Rule rule);
+
+ void enterStatement(Statement statement);
+
+ void exitStatement(Statement statement);
+
+ void enterFunctionCallStatement(FunctionStatement func);
+
+ void exitFunctionCallStatement(FunctionStatement func);
+
+ void enterVariableAssignStatement(VarAssignStatement assign);
+
+ void exitVariableAssignStatement(VarAssignStatement assign);
+
+ void enterAddition(AdditionExpression expr);
+
+ void exitAddition(AdditionExpression expr);
+
+ void enterAnd(AndExpression expr);
+
+ void exitAnd(AndExpression expr);
+
+ void enterArrayLiteral(ArrayLiteralExpression expr);
+
+ void exitArrayLiteral(ArrayLiteralExpression expr);
+
+ void enterBinary(BinaryExpression expr);
+
+ void exitBinary(BinaryExpression expr);
+
+ void enterBoolean(BooleanExpression expr);
+
+ void exitBoolean(BooleanExpression expr);
+
+ void enterBooleanFuncWrapper(BooleanValuedFunctionWrapper expr);
+
+ void exitBooleanFuncWrapper(BooleanValuedFunctionWrapper expr);
+
+ void enterComparison(ComparisonExpression expr);
+
+ void exitComparison(ComparisonExpression expr);
+
+ void enterConstant(ConstantExpression expr);
+
+ void exitConstant(ConstantExpression expr);
+
+ void enterDouble(DoubleExpression expr);
+
+ void exitDouble(DoubleExpression expr);
+
+ void enterEquality(EqualityExpression expr);
+
+ void exitEquality(EqualityExpression expr);
+
+ void enterFieldAccess(FieldAccessExpression expr);
+
+ void exitFieldAccess(FieldAccessExpression expr);
+
+ void enterFieldRef(FieldRefExpression expr);
+
+ void exitFieldRef(FieldRefExpression expr);
+
+ void enterFunctionCall(FunctionExpression expr);
+
+ void exitFunctionCall(FunctionExpression expr);
+
+ void enterIndexedAccess(IndexedAccessExpression expr);
+
+ void exitIndexedAccess(IndexedAccessExpression expr);
+
+ void enterLogical(LogicalExpression expr);
+
+ void exitLogical(LogicalExpression expr);
+
+ void enterLong(LongExpression expr);
+
+ void exitLong(LongExpression expr);
+
+ void enterMapLiteral(MapLiteralExpression expr);
+
+ void exitMapLiteral(MapLiteralExpression expr);
+
+ void enterMessageRef(MessageRefExpression expr);
+
+ void exitMessageRef(MessageRefExpression expr);
+
+ void enterMultiplication(MultiplicationExpression expr);
+
+ void exitMultiplication(MultiplicationExpression expr);
+
+ void enterNot(NotExpression expr);
+
+ void exitNot(NotExpression expr);
+
+ void enterNumeric(NumericExpression expr);
+
+ void exitNumeric(NumericExpression expr);
+
+ void enterOr(OrExpression expr);
+
+ void exitOr(OrExpression expr);
+
+ void enterSigned(SignedExpression expr);
+
+ void exitSigned(SignedExpression expr);
+
+ void enterString(StringExpression expr);
+
+ void exitString(StringExpression expr);
+
+ void enterUnary(UnaryExpression expr);
+
+ void exitUnary(UnaryExpression expr);
+
+ void enterVariableReference(VarRefExpression expr);
+
+ void exitVariableReference(VarRefExpression expr);
+
+ void enterEveryExpression(Expression expr);
+
+ void exitEveryExpression(Expression expr);
+
+ void enterFunctionArg(FunctionExpression functionExpression, Expression expression);
+
+ void exitFunctionArg(Expression expression);
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/RuleAstWalker.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/RuleAstWalker.java
new file mode 100644
index 000000000000..90740dc1b77d
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/RuleAstWalker.java
@@ -0,0 +1,270 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast;
+
+import org.graylog.plugins.pipelineprocessor.ast.expressions.AdditionExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.AndExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.ArrayLiteralExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.BinaryExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.BooleanExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.BooleanValuedFunctionWrapper;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.ComparisonExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.ConstantExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.DoubleExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.EqualityExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.Expression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.FieldAccessExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.FieldRefExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.FunctionExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.IndexedAccessExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.LogicalExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.LongExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.MapLiteralExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.MessageRefExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.MultiplicationExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.NotExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.NumericExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.OrExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.SignedExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.StringExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.UnaryExpression;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.VarRefExpression;
+import org.graylog.plugins.pipelineprocessor.ast.statements.FunctionStatement;
+import org.graylog.plugins.pipelineprocessor.ast.statements.Statement;
+import org.graylog.plugins.pipelineprocessor.ast.statements.VarAssignStatement;
+
+import java.util.Collection;
+
+public class RuleAstWalker {
+
+ public void walk(RuleAstListener listener, Rule rule) {
+ listener.enterRule(rule);
+
+ listener.enterWhen(rule);
+
+ walkExpression(listener, rule.when());
+
+ listener.exitWhen(rule);
+
+ listener.enterThen(rule);
+
+ walkStatements(listener, rule.then());
+
+ listener.exitThen(rule);
+
+ listener.exitRule(rule);
+ }
+
+ private void walkExpression(RuleAstListener listener, Expression expr) {
+ listener.enterEveryExpression(expr);
+ triggerAbstractEnter(listener, expr);
+ switch (expr.nodeType()) {
+ case ADD:
+ listener.enterAddition((AdditionExpression) expr);
+ visitChildren(listener, expr);
+ listener.exitAddition((AdditionExpression) expr);
+ break;
+ case AND:
+ listener.enterAnd((AndExpression) expr);
+ visitChildren(listener, expr);
+ listener.exitAnd((AndExpression) expr);
+ break;
+ case ARRAY_LITERAL:
+ listener.enterArrayLiteral((ArrayLiteralExpression) expr);
+ visitChildren(listener, expr);
+ listener.exitArrayLiteral((ArrayLiteralExpression) expr);
+ break;
+ case BINARY:
+ // special, handled as wrapper type in triggerAbstractEnter/Exit
+ break;
+ case BOOLEAN:
+ listener.enterBoolean((BooleanExpression) expr);
+ visitChildren(listener, expr);
+ listener.exitBoolean((BooleanExpression) expr);
+ break;
+ case BOOLEAN_FUNC_WRAPPER:
+ listener.enterBooleanFuncWrapper((BooleanValuedFunctionWrapper) expr);
+ visitChildren(listener, expr);
+ listener.exitBooleanFuncWrapper((BooleanValuedFunctionWrapper) expr);
+ break;
+ case COMPARISON:
+ listener.enterComparison((ComparisonExpression) expr);
+ visitChildren(listener, expr);
+ listener.exitComparison((ComparisonExpression) expr);
+ break;
+ case CONSTANT:
+ // special, handled as wrapper type in triggerAbstractEnter/Exit
+ break;
+ case DOUBLE:
+ listener.enterDouble((DoubleExpression) expr);
+ visitChildren(listener, expr);
+ listener.exitDouble((DoubleExpression) expr);
+ break;
+ case EQUALITY:
+ listener.enterEquality((EqualityExpression) expr);
+ visitChildren(listener, expr);
+ listener.exitEquality((EqualityExpression) expr);
+ break;
+ case FIELD_ACCESS:
+ listener.enterFieldAccess((FieldAccessExpression) expr);
+ visitChildren(listener, expr);
+ listener.exitFieldAccess((FieldAccessExpression) expr);
+ break;
+ case FIELD_REF:
+ listener.enterFieldRef((FieldRefExpression) expr);
+ visitChildren(listener, expr);
+ listener.exitFieldRef((FieldRefExpression) expr);
+ break;
+ case FUNCTION:
+ listener.enterFunctionCall((FunctionExpression) expr);
+ // special case, we want to wrap each function argument's expressing into its own
+ // callback, so we can generate statements for them.
+ expr.children().forEach(expression -> {
+ listener.enterFunctionArg((FunctionExpression) expr, expression);
+ walkExpression(listener, expression);
+ listener.exitFunctionArg(expression);
+ });
+
+ listener.exitFunctionCall((FunctionExpression) expr);
+ break;
+ case INDEXED_ACCESS:
+ listener.enterIndexedAccess((IndexedAccessExpression) expr);
+ visitChildren(listener, expr);
+ listener.exitIndexedAccess((IndexedAccessExpression) expr);
+ break;
+ case LOGICAL:
+ // special, handled as wrapper type in triggerAbstractEnter/Exit
+ break;
+ case LONG:
+ listener.enterLong((LongExpression) expr);
+ visitChildren(listener, expr);
+ listener.exitLong((LongExpression) expr);
+ break;
+ case MAP_LITERAL:
+ listener.enterMapLiteral((MapLiteralExpression) expr);
+ visitChildren(listener, expr);
+ listener.exitMapLiteral((MapLiteralExpression) expr);
+ break;
+ case MESSAGE:
+ listener.enterMessageRef((MessageRefExpression) expr);
+ visitChildren(listener, expr);
+ listener.exitMessageRef((MessageRefExpression) expr);
+ break;
+ case MULT:
+ listener.enterMultiplication((MultiplicationExpression) expr);
+ visitChildren(listener, expr);
+ listener.exitMultiplication((MultiplicationExpression) expr);
+ break;
+ case NOT:
+ listener.enterNot((NotExpression) expr);
+ visitChildren(listener, expr);
+ listener.exitNot((NotExpression) expr);
+ break;
+ case NUMERIC:
+ // special, handled as wrapper type in triggerAbstractEnter/Exit
+ break;
+ case OR:
+ listener.enterOr((OrExpression) expr);
+ visitChildren(listener, expr);
+ listener.exitOr((OrExpression) expr);
+ break;
+ case SIGNED:
+ listener.enterSigned((SignedExpression) expr);
+ visitChildren(listener, expr);
+ listener.exitSigned((SignedExpression) expr);
+ break;
+ case STRING:
+ listener.enterString((StringExpression) expr);
+ visitChildren(listener, expr);
+ listener.exitString((StringExpression) expr);
+ break;
+ case UNARY:
+ // special, handled as wrapper type in triggerAbstractEnter/Exit
+ break;
+ case VAR_REF:
+ listener.enterVariableReference((VarRefExpression) expr);
+ visitChildren(listener, expr);
+ listener.exitVariableReference((VarRefExpression) expr);
+ break;
+ }
+ triggerAbstractExit(listener, expr);
+ listener.exitEveryExpression(expr);
+ }
+
+ private void triggerAbstractEnter(RuleAstListener listener, Expression expr) {
+
+ if (expr instanceof BinaryExpression) {
+ listener.enterBinary((BinaryExpression) expr);
+
+ } else if (expr instanceof UnaryExpression) { // must not be first in "else if" because "binary is instanceof unary"
+ listener.enterUnary((UnaryExpression) expr);
+ }
+ // for the others we trigger regardless whether it's a binary or unary expr
+ if (expr instanceof LogicalExpression) {
+ listener.enterLogical((LogicalExpression) expr);
+ }
+ if (expr instanceof NumericExpression) {
+ listener.enterNumeric((NumericExpression) expr);
+ }
+ if (expr instanceof ConstantExpression) {
+ listener.enterConstant((ConstantExpression) expr);
+ }
+ }
+
+ private void triggerAbstractExit(RuleAstListener listener, Expression expr) {
+ if (expr instanceof BinaryExpression) {
+ listener.exitBinary((BinaryExpression) expr);
+
+ } else if (expr instanceof UnaryExpression) { // must not be first in "else if" because "binary is instanceof unary"
+ listener.exitUnary((UnaryExpression) expr);
+ }
+ // for the others we trigger regardless whether it's a binary or unary expr
+ if (expr instanceof LogicalExpression) {
+ listener.exitLogical((LogicalExpression) expr);
+ }
+ if (expr instanceof NumericExpression) {
+ listener.exitNumeric((NumericExpression) expr);
+ }
+ if (expr instanceof ConstantExpression) {
+ listener.exitConstant((ConstantExpression) expr);
+ }
+ }
+
+ private void visitChildren(RuleAstListener listener, Expression expr) {
+ expr.children().forEach(expression -> walkExpression(listener, expression));
+ }
+
+ private void walkStatements(RuleAstListener listener, Collection statements) {
+ statements.forEach(statement -> {
+ listener.enterStatement(statement);
+
+ if (statement instanceof FunctionStatement) {
+ FunctionStatement func = (FunctionStatement) statement;
+ listener.enterFunctionCallStatement(func);
+ walkExpression(listener, func.getFunctionExpression());
+ listener.exitFunctionCallStatement(func);
+ } else if (statement instanceof VarAssignStatement) {
+ VarAssignStatement assign = (VarAssignStatement) statement;
+ listener.enterVariableAssignStatement(assign);
+ walkExpression(listener, assign.getValueExpression());
+ listener.exitVariableAssignStatement(assign);
+ }
+
+ listener.exitStatement(statement);
+ });
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/Stage.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/Stage.java
new file mode 100644
index 000000000000..1ef998d08913
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/Stage.java
@@ -0,0 +1,111 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast;
+
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricFilter;
+import com.codahale.metrics.MetricRegistry;
+import com.google.auto.value.AutoValue;
+import org.graylog2.shared.metrics.MetricUtils;
+
+import java.util.List;
+
+import static com.codahale.metrics.MetricRegistry.name;
+
+@AutoValue
+public abstract class Stage implements Comparable {
+ private List rules;
+ // not an autovalue property, because it introduces a cycle in hashCode() and we have no way of excluding it
+ private transient Pipeline pipeline;
+ private transient Meter executed;
+ private transient String meterName;
+
+ public abstract int stage();
+ public abstract boolean matchAll();
+ public abstract List ruleReferences();
+
+ public List getRules() {
+ return rules;
+ }
+
+ public void setRules(List rules) {
+ this.rules = rules;
+ }
+
+ public static Builder builder() {
+ return new AutoValue_Stage.Builder();
+ }
+
+ public abstract Builder toBuilder();
+
+ @Override
+ public int compareTo(@SuppressWarnings("NullableProblems") Stage other) {
+ return Integer.compare(stage(), other.stage());
+ }
+
+ /**
+ * Register the metrics attached to this stage.
+ *
+ * @param metricRegistry the registry to add the metrics to
+ */
+ public void registerMetrics(MetricRegistry metricRegistry, String pipelineId) {
+ meterName = name(Pipeline.class, pipelineId, "stage", String.valueOf(stage()), "executed");
+ executed = metricRegistry.meter(meterName);
+ }
+
+ /**
+ * The metric filter matching all metrics that have been registered by this pipeline.
+ * Commonly used to remove the relevant metrics from the registry upon deletion of the pipeline.
+ *
+ * @return the filter matching this pipeline's metrics
+ */
+ public MetricFilter metricsFilter() {
+ if (meterName == null) {
+ return (name, metric) -> false;
+ }
+ return new MetricUtils.SingleMetricFilter(meterName);
+
+ }
+ public void markExecution() {
+ if (executed != null) {
+ executed.mark();
+ }
+ }
+
+ public Pipeline getPipeline() {
+ return pipeline;
+ }
+
+ public void setPipeline(Pipeline pipeline) {
+ this.pipeline = pipeline;
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Stage build();
+
+ public abstract Builder stage(int stageNumber);
+
+ public abstract Builder matchAll(boolean mustMatchAll);
+
+ public abstract Builder ruleReferences(List ruleRefs);
+ }
+
+ public String toString() {
+ return "Stage " + stage();
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/exceptions/FunctionEvaluationException.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/exceptions/FunctionEvaluationException.java
new file mode 100644
index 000000000000..117a62865e4a
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/exceptions/FunctionEvaluationException.java
@@ -0,0 +1,38 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.exceptions;
+
+import org.graylog.plugins.pipelineprocessor.ast.expressions.FunctionExpression;
+
+public class FunctionEvaluationException extends LocationAwareEvalException {
+ private final FunctionExpression functionExpression;
+ private final Exception exception;
+
+ public FunctionEvaluationException(FunctionExpression functionExpression, Exception exception) {
+ super(functionExpression.getStartToken(), exception);
+ this.functionExpression = functionExpression;
+ this.exception = exception;
+ }
+
+ public FunctionExpression getFunctionExpression() {
+ return functionExpression;
+ }
+
+ public Exception getException() {
+ return exception;
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/exceptions/LocationAwareEvalException.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/exceptions/LocationAwareEvalException.java
new file mode 100644
index 000000000000..3ec325cd5e5d
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/exceptions/LocationAwareEvalException.java
@@ -0,0 +1,32 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.exceptions;
+
+import org.antlr.v4.runtime.Token;
+
+public class LocationAwareEvalException extends RuntimeException {
+ private final Token startToken;
+
+ public LocationAwareEvalException(Token startToken, Throwable cause) {
+ super(cause);
+ this.startToken = startToken;
+ }
+
+ public Token getStartToken() {
+ return startToken;
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/exceptions/PrecomputeFailure.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/exceptions/PrecomputeFailure.java
new file mode 100644
index 000000000000..1ff53bb94ffa
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/exceptions/PrecomputeFailure.java
@@ -0,0 +1,35 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.exceptions;
+
+public class PrecomputeFailure extends RuntimeException {
+ private final String argumentName;
+
+ public PrecomputeFailure(String argumentName, Exception cause) {
+ super(cause);
+ this.argumentName = argumentName;
+ }
+
+ public String getArgumentName() {
+ return argumentName;
+ }
+
+ @Override
+ public String getMessage() {
+ return "Unable to pre-compute argument " + getArgumentName() + ": " + getCause().getMessage();
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/AdditionExpression.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/AdditionExpression.java
new file mode 100644
index 000000000000..28f6efe9cdcf
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/AdditionExpression.java
@@ -0,0 +1,132 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import org.antlr.v4.runtime.Token;
+import org.graylog.plugins.pipelineprocessor.EvaluationContext;
+import org.joda.time.DateTime;
+import org.joda.time.Duration;
+import org.joda.time.Period;
+
+import javax.annotation.Nullable;
+
+import static com.google.common.base.MoreObjects.firstNonNull;
+
+public class AdditionExpression extends BinaryExpression implements NumericExpression {
+ private final boolean isPlus;
+ private Class type = Void.class;
+
+ public AdditionExpression(Token start, Expression left, Expression right, boolean isPlus) {
+ super(start, left, right);
+ this.isPlus = isPlus;
+ }
+
+ @Override
+ public boolean isIntegral() {
+ return getType().equals(Long.class);
+ }
+
+ @Override
+ public long evaluateLong(EvaluationContext context) {
+ return (long) firstNonNull(evaluateUnsafe(context), 0);
+ }
+
+ @Override
+ public double evaluateDouble(EvaluationContext context) {
+ return (double) firstNonNull(evaluateUnsafe(context), 0d);
+ }
+
+ @Nullable
+ @Override
+ public Object evaluateUnsafe(EvaluationContext context) {
+ final Object leftValue = left.evaluateUnsafe(context);
+ final Object rightValue = right.evaluateUnsafe(context);
+
+ // special case for date arithmetic
+ final boolean leftDate = DateTime.class.equals(leftValue.getClass());
+ final boolean leftPeriod = Period.class.equals(leftValue.getClass());
+ final boolean rightDate = DateTime.class.equals(rightValue.getClass());
+ final boolean rightPeriod = Period.class.equals(rightValue.getClass());
+
+ if (leftDate && rightPeriod) {
+ final DateTime date = (DateTime) leftValue;
+ final Period period = (Period) rightValue;
+
+ return isPlus() ? date.plus(period) : date.minus(period);
+ } else if (leftPeriod && rightDate) {
+ final DateTime date = (DateTime) rightValue;
+ final Period period = (Period) leftValue;
+
+ return isPlus() ? date.plus(period) : date.minus(period);
+ } else if (leftPeriod && rightPeriod) {
+ final Period period1 = (Period) leftValue;
+ final Period period2 = (Period) rightValue;
+
+ return isPlus() ? period1.plus(period2) : period1.minus(period2);
+ } else if (leftDate && rightDate) {
+ // the most uncommon, this is only defined for - really and means "interval between them"
+ // because adding two dates makes no sense
+ if (isPlus()) {
+ // makes no sense to compute and should be handles in the parser already
+ return null;
+ }
+ final DateTime left = (DateTime) leftValue;
+ final DateTime right = (DateTime) rightValue;
+
+ if (left.isBefore(right)) {
+ return new Duration(left, right);
+ } else {
+ return new Duration(right, left);
+ }
+ }
+ if (isIntegral()) {
+ final long l = (long) leftValue;
+ final long r = (long) rightValue;
+ if (isPlus) {
+ return l + r;
+ } else {
+ return l - r;
+ }
+ } else {
+ final double l = (double) leftValue;
+ final double r = (double) rightValue;
+ if (isPlus) {
+ return l + r;
+ } else {
+ return l - r;
+ }
+ }
+ }
+
+ public boolean isPlus() {
+ return isPlus;
+ }
+
+ @Override
+ public Class getType() {
+ return type;
+ }
+
+ public void setType(Class type) {
+ this.type = type;
+ }
+
+ @Override
+ public String toString() {
+ return left.toString() + (isPlus ? " + " : " - ") + right.toString();
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/AndExpression.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/AndExpression.java
new file mode 100644
index 000000000000..fe9e10999063
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/AndExpression.java
@@ -0,0 +1,47 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import org.antlr.v4.runtime.Token;
+import org.graylog.plugins.pipelineprocessor.EvaluationContext;
+
+public class AndExpression extends BinaryExpression implements LogicalExpression {
+ public AndExpression(Token start, Expression left,
+ Expression right) {
+ super(start, left, right);
+ }
+
+ @Override
+ public Object evaluateUnsafe(EvaluationContext context) {
+ return evaluateBool(context);
+ }
+
+ @Override
+ public boolean evaluateBool(EvaluationContext context) {
+ return ((LogicalExpression)left).evaluateBool(context) && ((LogicalExpression)right).evaluateBool(context);
+ }
+
+ @Override
+ public Class getType() {
+ return Boolean.class;
+ }
+
+ @Override
+ public String toString() {
+ return left.toString() + " AND " + right.toString();
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/ArrayLiteralExpression.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/ArrayLiteralExpression.java
new file mode 100644
index 000000000000..c77b01c78449
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/ArrayLiteralExpression.java
@@ -0,0 +1,62 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+
+import org.antlr.v4.runtime.Token;
+import org.graylog.plugins.pipelineprocessor.EvaluationContext;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class ArrayLiteralExpression extends BaseExpression {
+ private final List elements;
+
+ public ArrayLiteralExpression(Token start, List elements) {
+ super(start);
+ this.elements = elements;
+ }
+
+ @Override
+ public boolean isConstant() {
+ return elements.stream().allMatch(Expression::isConstant);
+ }
+
+ @Override
+ public List evaluateUnsafe(EvaluationContext context) {
+ return elements.stream()
+ .map(expression -> expression.evaluateUnsafe(context))
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public Class getType() {
+ return List.class;
+ }
+
+ @Override
+ public String toString() {
+ return "[" + Joiner.on(", ").join(elements) + "]";
+ }
+
+ @Override
+ public Iterable children() {
+ return ImmutableList.copyOf(elements);
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/BaseExpression.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/BaseExpression.java
new file mode 100644
index 000000000000..0868c58f63ed
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/BaseExpression.java
@@ -0,0 +1,34 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import org.antlr.v4.runtime.Token;
+
+public abstract class BaseExpression implements Expression {
+
+ private final Token startToken;
+
+ public BaseExpression(Token startToken) {
+ this.startToken = startToken;
+ }
+
+ @Override
+ public Token getStartToken() {
+ return startToken;
+ }
+
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/BinaryExpression.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/BinaryExpression.java
new file mode 100644
index 000000000000..84f71edf6318
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/BinaryExpression.java
@@ -0,0 +1,48 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import com.google.common.collect.ImmutableList;
+
+import org.antlr.v4.runtime.Token;
+
+public abstract class BinaryExpression extends UnaryExpression {
+
+ protected Expression left;
+
+ public BinaryExpression(Token start, Expression left, Expression right) {
+ super(start, right);
+ this.left = left;
+ }
+
+ @Override
+ public boolean isConstant() {
+ return left.isConstant() && right.isConstant();
+ }
+
+ public Expression left() {
+ return left;
+ }
+
+ public void left(Expression left) {
+ this.left = left;
+ }
+ @Override
+ public Iterable children() {
+ return ImmutableList.of(left, right);
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/BooleanExpression.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/BooleanExpression.java
new file mode 100644
index 000000000000..55fb7b67b9bd
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/BooleanExpression.java
@@ -0,0 +1,45 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import org.antlr.v4.runtime.Token;
+import org.graylog.plugins.pipelineprocessor.EvaluationContext;
+
+public class BooleanExpression extends ConstantExpression implements LogicalExpression {
+ private final boolean value;
+
+ public BooleanExpression(Token start, boolean value) {
+ super(start, Boolean.class);
+ this.value = value;
+ }
+
+ @Override
+ public Object evaluateUnsafe(EvaluationContext context) {
+ return value;
+ }
+
+
+ @Override
+ public boolean evaluateBool(EvaluationContext context) {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return Boolean.toString(value);
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/BooleanValuedFunctionWrapper.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/BooleanValuedFunctionWrapper.java
new file mode 100644
index 000000000000..ba1c26b638f7
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/BooleanValuedFunctionWrapper.java
@@ -0,0 +1,69 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import org.antlr.v4.runtime.Token;
+import org.graylog.plugins.pipelineprocessor.EvaluationContext;
+
+import java.util.Collections;
+
+public class BooleanValuedFunctionWrapper extends BaseExpression implements LogicalExpression {
+ private final Expression expr;
+
+ public BooleanValuedFunctionWrapper(Token start, Expression expr) {
+ super(start);
+ this.expr = expr;
+ if (!expr.getType().equals(Boolean.class)) {
+ throw new IllegalArgumentException("expr must be of boolean type");
+ }
+ }
+
+ @Override
+ public boolean evaluateBool(EvaluationContext context) {
+ final Object value = expr.evaluateUnsafe(context);
+ return value != null && (Boolean) value;
+ }
+
+ @Override
+ public boolean isConstant() {
+ return expr.isConstant();
+ }
+
+ @Override
+ public Object evaluateUnsafe(EvaluationContext context) {
+ return evaluateBool(context);
+ }
+
+ @Override
+ public Class getType() {
+ return expr.getType();
+ }
+
+ public Expression expression() {
+ return expr;
+ }
+
+ @Override
+ public String toString() {
+ return expr.toString();
+ }
+
+ @Override
+ public Iterable children() {
+ return Collections.singleton(expr);
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/ComparisonExpression.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/ComparisonExpression.java
new file mode 100644
index 000000000000..5bb6e5174aca
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/ComparisonExpression.java
@@ -0,0 +1,112 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import org.antlr.v4.runtime.Token;
+import org.graylog.plugins.pipelineprocessor.EvaluationContext;
+import org.joda.time.DateTime;
+
+public class ComparisonExpression extends BinaryExpression implements LogicalExpression {
+ private final String operator;
+
+ public ComparisonExpression(Token start, Expression left, Expression right, String operator) {
+ super(start, left, right);
+ this.operator = operator;
+ }
+
+ @Override
+ public Object evaluateUnsafe(EvaluationContext context) {
+ return evaluateBool(context);
+ }
+
+ @Override
+ public Class getType() {
+ return Boolean.class;
+ }
+
+ @Override
+ public boolean evaluateBool(EvaluationContext context) {
+
+ final Object leftValue = this.left.evaluateUnsafe(context);
+ final Object rightValue = this.right.evaluateUnsafe(context);
+ if (leftValue instanceof DateTime && rightValue instanceof DateTime) {
+ return compareDateTimes(operator, (DateTime) leftValue, (DateTime) rightValue);
+ }
+
+ if (leftValue instanceof Double || rightValue instanceof Double) {
+ return compareDouble(operator, (double) leftValue, (double) rightValue);
+ }
+
+ return compareLong(operator, (long) leftValue, (long) rightValue);
+ }
+
+ @SuppressWarnings("Duplicates")
+ private boolean compareLong(String operator, long left, long right) {
+ switch (operator) {
+ case ">":
+ return left > right;
+ case ">=":
+ return left >= right;
+ case "<":
+ return left < right;
+ case "<=":
+ return left <= right;
+ default:
+ return false;
+ }
+ }
+
+ @SuppressWarnings("Duplicates")
+ private boolean compareDouble(String operator, double left, double right) {
+ switch (operator) {
+ case ">":
+ return left > right;
+ case ">=":
+ return left >= right;
+ case "<":
+ return left < right;
+ case "<=":
+ return left <= right;
+ default:
+ return false;
+ }
+ }
+
+ private boolean compareDateTimes(String operator, DateTime left, DateTime right) {
+ switch (operator) {
+ case ">":
+ return left.isAfter(right);
+ case ">=":
+ return !left.isBefore(right);
+ case "<":
+ return left.isBefore(right);
+ case "<=":
+ return !left.isAfter(right);
+ default:
+ return false;
+ }
+ }
+
+ public String getOperator() {
+ return operator;
+ }
+
+ @Override
+ public String toString() {
+ return left.toString() + " " + operator + " " + right.toString();
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/ConstantExpression.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/ConstantExpression.java
new file mode 100644
index 000000000000..6f11e63e970f
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/ConstantExpression.java
@@ -0,0 +1,46 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import org.antlr.v4.runtime.Token;
+
+import java.util.Collections;
+
+public abstract class ConstantExpression extends BaseExpression {
+
+ private final Class type;
+
+ protected ConstantExpression(Token start, Class type) {
+ super(start);
+ this.type = type;
+ }
+
+ @Override
+ public boolean isConstant() {
+ return true;
+ }
+
+ @Override
+ public Class getType() {
+ return type;
+ }
+
+ @Override
+ public Iterable children() {
+ return Collections.emptySet();
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/DoubleExpression.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/DoubleExpression.java
new file mode 100644
index 000000000000..030d131a969b
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/DoubleExpression.java
@@ -0,0 +1,54 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import org.antlr.v4.runtime.Token;
+import org.graylog.plugins.pipelineprocessor.EvaluationContext;
+
+public class DoubleExpression extends ConstantExpression implements NumericExpression {
+ private final double value;
+
+ public DoubleExpression(Token start, double value) {
+ super(start, Double.class);
+ this.value = value;
+ }
+
+ @Override
+ public Object evaluateUnsafe(EvaluationContext context) {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return Double.toString(value);
+ }
+
+ @Override
+ public boolean isIntegral() {
+ return false;
+ }
+
+ @Override
+ public long evaluateLong(EvaluationContext context) {
+ return (long) value;
+ }
+
+ @Override
+ public double evaluateDouble(EvaluationContext context) {
+ return value;
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/EqualityExpression.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/EqualityExpression.java
new file mode 100644
index 000000000000..fa4cae814fd1
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/EqualityExpression.java
@@ -0,0 +1,88 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import org.antlr.v4.runtime.Token;
+import org.graylog.plugins.pipelineprocessor.EvaluationContext;
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class EqualityExpression extends BinaryExpression implements LogicalExpression {
+ private static final Logger log = LoggerFactory.getLogger(EqualityExpression.class);
+
+ private final boolean checkEquality;
+
+ public EqualityExpression(Token start, Expression left, Expression right, boolean checkEquality) {
+ super(start, left, right);
+ this.checkEquality = checkEquality;
+ }
+
+ @Override
+ public Object evaluateUnsafe(EvaluationContext context) {
+ return evaluateBool(context);
+ }
+
+ @Override
+ public Class getType() {
+ return Boolean.class;
+ }
+
+ @Override
+ public boolean evaluateBool(EvaluationContext context) {
+ final Object left = this.left.evaluateUnsafe(context);
+ final Object right = this.right.evaluateUnsafe(context);
+ if (left == null) {
+ log.warn("left expression evaluated to null, returning false: {}", this.left);
+ return false;
+ }
+ final boolean equals;
+ // sigh: DateTime::equals takes the chronology into account, so identical instants expressed in different timezones are not equal
+ if (left instanceof DateTime && right instanceof DateTime) {
+ equals = ((DateTime) left).isEqual((DateTime) right);
+ } else {
+ equals = left.equals(right);
+ }
+
+ if (log.isTraceEnabled()) {
+ traceEquality(left, right, equals, checkEquality);
+ }
+ if (checkEquality) {
+ return equals;
+ }
+ return !equals;
+ }
+
+ private void traceEquality(Object left,
+ Object right,
+ boolean equals,
+ boolean checkEquality) {
+ log.trace(checkEquality
+ ? "[{}] {} == {} : {} == {}"
+ : "[{}] {} != {} : {} != {}",
+ checkEquality == equals, this.left, this.right, left, right);
+ }
+
+ public boolean isCheckEquality() {
+ return checkEquality;
+ }
+
+ @Override
+ public String toString() {
+ return left.toString() + (checkEquality ? " == " : " != ") + right.toString();
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/Expression.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/Expression.java
new file mode 100644
index 000000000000..04de2614e17f
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/Expression.java
@@ -0,0 +1,122 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Maps;
+
+import org.antlr.v4.runtime.Token;
+import org.graylog.plugins.pipelineprocessor.EvaluationContext;
+import org.graylog.plugins.pipelineprocessor.ast.exceptions.FunctionEvaluationException;
+
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+import static org.graylog2.shared.utilities.ExceptionUtils.getRootCause;
+
+public interface Expression {
+
+ boolean isConstant();
+
+ Token getStartToken();
+
+ @Nullable
+ default Object evaluate(EvaluationContext context) {
+ try {
+ return evaluateUnsafe(context);
+ } catch (FunctionEvaluationException fee) {
+ context.addEvaluationError(fee.getStartToken().getLine(),
+ fee.getStartToken().getCharPositionInLine(),
+ fee.getFunctionExpression().getFunction().descriptor(),
+ getRootCause(fee));
+ } catch (Exception e) {
+ context.addEvaluationError(getStartToken().getLine(), getStartToken().getCharPositionInLine(), null, getRootCause(e));
+ }
+ return null;
+ }
+
+ Class getType();
+
+ /**
+ * This method is allowed to throw exceptions. The outside world is supposed to call evaluate instead.
+ */
+ @Nullable
+ Object evaluateUnsafe(EvaluationContext context);
+
+ /**
+ * This method is allowed to throw exceptions and evaluates the expression in an empty context.
+ * It is only useful for the interpreter/code generator to evaluate constant expressions to their effective value.
+ *
+ * @return the value of the expression in an empty context
+ */
+ default Object evaluateUnsafe() {
+ return evaluateUnsafe(EvaluationContext.emptyContext());
+ }
+
+ Iterable children();
+
+ default Type nodeType() {
+ return Type.fromClass(this.getClass());
+ }
+
+ // helper to aid switching over the available expression node types
+ enum Type {
+ ADD(AdditionExpression.class),
+ AND(AndExpression.class),
+ ARRAY_LITERAL(ArrayLiteralExpression.class),
+ BINARY(BinaryExpression.class),
+ BOOLEAN(BooleanExpression.class),
+ BOOLEAN_FUNC_WRAPPER(BooleanValuedFunctionWrapper.class),
+ COMPARISON(ComparisonExpression.class),
+ CONSTANT(ConstantExpression.class),
+ DOUBLE(DoubleExpression.class),
+ EQUALITY(EqualityExpression.class),
+ FIELD_ACCESS(FieldAccessExpression.class),
+ FIELD_REF(FieldRefExpression.class),
+ FUNCTION(FunctionExpression.class),
+ INDEXED_ACCESS(IndexedAccessExpression.class),
+ LOGICAL(LogicalExpression.class),
+ LONG(LongExpression.class),
+ MAP_LITERAL(MapLiteralExpression.class),
+ MESSAGE(MessageRefExpression.class),
+ MULT(MultiplicationExpression.class),
+ NOT(NotExpression.class),
+ NUMERIC(NumericExpression.class),
+ OR(OrExpression.class),
+ SIGNED(SignedExpression.class),
+ STRING(StringExpression.class),
+ UNARY(UnaryExpression.class),
+ VAR_REF(VarRefExpression.class);
+
+ static Map classMap;
+
+ static {
+ classMap = Maps.uniqueIndex(Iterators.forArray(Type.values()), type -> type.klass);
+ }
+
+ private final Class extends Expression> klass;
+
+ Type(Class extends Expression> expressionClass) {
+ klass = expressionClass;
+ }
+
+ static Type fromClass(Class klass) {
+ return classMap.get(klass);
+ }
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/FieldAccessExpression.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/FieldAccessExpression.java
new file mode 100644
index 000000000000..1e97108df966
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/FieldAccessExpression.java
@@ -0,0 +1,102 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import com.google.common.base.CaseFormat;
+import com.google.common.collect.ImmutableList;
+import org.antlr.v4.runtime.Token;
+import org.apache.commons.beanutils.PropertyUtils;
+import org.graylog.plugins.pipelineprocessor.EvaluationContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.InvocationTargetException;
+
+public class FieldAccessExpression extends BaseExpression {
+ private static final Logger log = LoggerFactory.getLogger(FieldAccessExpression.class);
+
+ private final Expression object;
+ private final Expression field;
+
+ public FieldAccessExpression(Token start, Expression object, Expression field) {
+ super(start);
+ this.object = object;
+ this.field = field;
+ }
+
+ @Override
+ public boolean isConstant() {
+ return false;
+ }
+
+ @Override
+ public Object evaluateUnsafe(EvaluationContext context) {
+ final Object bean = this.object.evaluateUnsafe(context);
+ final Object fieldValue = field.evaluateUnsafe(context);
+ if (bean == null || fieldValue == null) {
+ return null;
+ }
+ final String fieldName = fieldValue.toString();
+
+ // First try to access the field using the given field name
+ final Object property = getProperty(bean, fieldName);
+ if (property == null) {
+ // If the given field name does not work, try to convert it to camel case to make JSON-like access
+ // to fields possible. Example: "geo.location.metro_code" => "geo.getLocation().getMetroCode()"
+ return getProperty(bean, CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, fieldName));
+ }
+ return property;
+ }
+
+ private Object getProperty(Object bean, String fieldName) {
+ try {
+ Object property = PropertyUtils.getProperty(bean, fieldName);
+ if (property == null) {
+ // in case the bean is a Map, try again with a simple property, it might be masked by the Map
+ property = PropertyUtils.getSimpleProperty(bean, fieldName);
+ }
+ log.debug("[field access] property {} of bean {}: {}", fieldName, bean.getClass().getTypeName(), property);
+ return property;
+ } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
+ log.debug("Unable to read property {} from {}", fieldName, bean);
+ return null;
+ }
+ }
+
+ @Override
+ public Class getType() {
+ return Object.class;
+ }
+
+ @Override
+ public String toString() {
+ return object.toString() + "." + field.toString();
+ }
+
+ public Expression object() {
+ return object;
+ }
+
+ public Expression field() {
+ return field;
+ }
+
+ @Override
+ public Iterable children() {
+ return ImmutableList.of(object, field);
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/FieldRefExpression.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/FieldRefExpression.java
new file mode 100644
index 000000000000..ffe14b1390f4
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/FieldRefExpression.java
@@ -0,0 +1,62 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import org.antlr.v4.runtime.Token;
+import org.graylog.plugins.pipelineprocessor.EvaluationContext;
+
+import java.util.Collections;
+
+public class FieldRefExpression extends BaseExpression {
+ private final String variableName;
+ private final Expression fieldExpr;
+
+ public FieldRefExpression(Token start, String variableName, Expression fieldExpr) {
+ super(start);
+ this.variableName = variableName;
+ this.fieldExpr = fieldExpr;
+ }
+
+ @Override
+ public boolean isConstant() {
+ return true;
+ }
+
+ @Override
+ public Object evaluateUnsafe(EvaluationContext context) {
+ return variableName;
+ }
+
+ @Override
+ public Class getType() {
+ return String.class;
+ }
+
+ @Override
+ public String toString() {
+ return variableName;
+ }
+
+ public String fieldName() {
+ return variableName;
+ }
+
+ @Override
+ public Iterable children() {
+ return Collections.emptySet();
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/FunctionExpression.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/FunctionExpression.java
new file mode 100644
index 000000000000..15592c6fb179
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/FunctionExpression.java
@@ -0,0 +1,95 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import com.google.common.base.Joiner;
+
+import org.antlr.v4.runtime.Token;
+import org.graylog.plugins.pipelineprocessor.EvaluationContext;
+import org.graylog.plugins.pipelineprocessor.ast.exceptions.FunctionEvaluationException;
+import org.graylog.plugins.pipelineprocessor.ast.exceptions.LocationAwareEvalException;
+import org.graylog.plugins.pipelineprocessor.ast.functions.Function;
+import org.graylog.plugins.pipelineprocessor.ast.functions.FunctionArgs;
+import org.graylog.plugins.pipelineprocessor.ast.functions.FunctionDescriptor;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class FunctionExpression extends BaseExpression {
+ private final FunctionArgs args;
+ private final Function> function;
+ private final FunctionDescriptor descriptor;
+
+ public FunctionExpression(Token start, FunctionArgs args) {
+ super(start);
+ this.args = args;
+ this.function = args.getFunction();
+ this.descriptor = this.function.descriptor();
+
+ // precomputes all constant arguments to avoid dynamically recomputing trees on every invocation
+ this.function.preprocessArgs(args);
+ }
+
+ public Function> getFunction() {
+ return function;
+ }
+
+ public FunctionArgs getArgs() {
+ return args;
+ }
+
+ @Override
+ public boolean isConstant() {
+ return false;
+ }
+
+ @Override
+ public Object evaluateUnsafe(EvaluationContext context) {
+ try {
+ return descriptor.returnType().cast(function.evaluate(args, context));
+ } catch (LocationAwareEvalException laee) {
+ // the exception already has a location from the input source, simply propagate it.
+ throw laee;
+ } catch (Exception e) {
+ // we need to wrap the original exception to retain the position in the tree where the exception originated
+ throw new FunctionEvaluationException(this, e);
+ }
+ }
+
+ @Override
+ public Class getType() {
+ return descriptor.returnType();
+ }
+
+ @Override
+ public String toString() {
+ String argsString = "";
+ if (args != null) {
+ argsString = Joiner.on(", ")
+ .withKeyValueSeparator(": ")
+ .join(args.getArgs().entrySet().stream()
+ .sorted((e1, e2) -> e1.getKey().compareTo(e2.getKey()))
+ .iterator());
+ }
+ return descriptor.name() + "(" + argsString + ")";
+ }
+
+ @Override
+ public Iterable children() {
+ return args.getArgs().entrySet().stream().map(Map.Entry::getValue).collect(Collectors.toList());
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/IndexedAccessExpression.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/IndexedAccessExpression.java
new file mode 100644
index 000000000000..10bfba333417
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/IndexedAccessExpression.java
@@ -0,0 +1,94 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.primitives.Ints;
+import org.antlr.v4.runtime.Token;
+import org.graylog.plugins.pipelineprocessor.EvaluationContext;
+
+import java.lang.reflect.Array;
+import java.util.List;
+import java.util.Map;
+
+public class IndexedAccessExpression extends BaseExpression {
+ private final Expression indexableObject;
+ private final Expression index;
+
+ public IndexedAccessExpression(Token start, Expression indexableObject, Expression index) {
+ super(start);
+ this.indexableObject = indexableObject;
+ this.index = index;
+ }
+
+ @Override
+ public boolean isConstant() {
+ return indexableObject.isConstant() && index.isConstant();
+ }
+
+ @Override
+ public Object evaluateUnsafe(EvaluationContext context) {
+ final Object idxObj = this.index.evaluateUnsafe(context);
+ final Object indexable = indexableObject.evaluateUnsafe(context);
+ if (idxObj == null || indexable == null) {
+ return null;
+ }
+
+ if (idxObj instanceof Long) {
+ int idx = Ints.saturatedCast((long) idxObj);
+ if (indexable.getClass().isArray()) {
+ return Array.get(indexable, idx);
+ } else if (indexable instanceof List) {
+ return ((List) indexable).get(idx);
+ } else if (indexable instanceof Iterable) {
+ return Iterables.get((Iterable) indexable, idx);
+ }
+ throw new IllegalArgumentException("Object '" + indexable + "' is not an Array, List or Iterable.");
+ } else if (idxObj instanceof String) {
+ final String idx = idxObj.toString();
+ if (indexable instanceof Map) {
+ return ((Map) indexable).get(idx);
+ }
+ throw new IllegalArgumentException("Object '" + indexable + "' is not a Map.");
+ }
+ throw new IllegalArgumentException("Index '" + idxObj + "' is not a Long or String.");
+ }
+
+ @Override
+ public Class getType() {
+ return Object.class;
+ }
+
+ @Override
+ public String toString() {
+ return indexableObject.toString() + "[" + index.toString() + "]";
+ }
+
+ public Expression getIndexableObject() {
+ return indexableObject;
+ }
+
+ public Expression getIndex() {
+ return index;
+ }
+
+ @Override
+ public Iterable children() {
+ return ImmutableList.of(indexableObject, index);
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/LogicalExpression.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/LogicalExpression.java
new file mode 100644
index 000000000000..608c0e7eda67
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/LogicalExpression.java
@@ -0,0 +1,24 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import org.graylog.plugins.pipelineprocessor.EvaluationContext;
+
+public interface LogicalExpression extends Expression {
+
+ boolean evaluateBool(EvaluationContext context);
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/LongExpression.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/LongExpression.java
new file mode 100644
index 000000000000..ad49fd6822e8
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/LongExpression.java
@@ -0,0 +1,54 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import org.antlr.v4.runtime.Token;
+import org.graylog.plugins.pipelineprocessor.EvaluationContext;
+
+public class LongExpression extends ConstantExpression implements NumericExpression {
+ private final long value;
+
+ public LongExpression(Token start, long value) {
+ super(start, Long.class);
+ this.value = value;
+ }
+
+ @Override
+ public Object evaluateUnsafe(EvaluationContext context) {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return Long.toString(value);
+ }
+
+ @Override
+ public boolean isIntegral() {
+ return true;
+ }
+
+ @Override
+ public long evaluateLong(EvaluationContext context) {
+ return value;
+ }
+
+ @Override
+ public double evaluateDouble(EvaluationContext context) {
+ return value;
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/MapLiteralExpression.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/MapLiteralExpression.java
new file mode 100644
index 000000000000..f55aaba64f65
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/MapLiteralExpression.java
@@ -0,0 +1,70 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+import org.antlr.v4.runtime.Token;
+import org.graylog.plugins.pipelineprocessor.EvaluationContext;
+import org.jooq.lambda.Seq;
+import org.jooq.lambda.tuple.Tuple2;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class MapLiteralExpression extends BaseExpression {
+ private final HashMap map;
+
+ public MapLiteralExpression(Token start, HashMap map) {
+ super(start);
+ this.map = map;
+ }
+
+ @Override
+ public boolean isConstant() {
+ return map.values().stream().allMatch(Expression::isConstant);
+ }
+
+ @Override
+ public Map evaluateUnsafe(EvaluationContext context) {
+ // evaluate all values for each key and return the resulting map
+ return Seq.seq(map)
+ .map(entry -> entry.map2(value -> value.evaluateUnsafe(context)))
+ .toMap(Tuple2::v1, Tuple2::v2);
+ }
+
+ @Override
+ public Class getType() {
+ return Map.class;
+ }
+
+ @Override
+ public String toString() {
+ return "{" + Joiner.on(", ").withKeyValueSeparator(":").join(map) + "}";
+ }
+
+ public Iterable> entries() {
+ return ImmutableSet.copyOf(map.entrySet());
+ }
+
+ @Override
+ public Iterable children() {
+ return ImmutableList.copyOf(map.values());
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/MessageRefExpression.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/MessageRefExpression.java
new file mode 100644
index 000000000000..056dee312e80
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/MessageRefExpression.java
@@ -0,0 +1,64 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import org.antlr.v4.runtime.Token;
+import org.graylog.plugins.pipelineprocessor.EvaluationContext;
+
+import java.util.Collections;
+
+public class MessageRefExpression extends BaseExpression {
+ private final Expression fieldExpr;
+
+ public MessageRefExpression(Token start, Expression fieldExpr) {
+ super(start);
+ this.fieldExpr = fieldExpr;
+ }
+
+ @Override
+ public boolean isConstant() {
+ return false;
+ }
+
+ @Override
+ public Object evaluateUnsafe(EvaluationContext context) {
+ final Object fieldName = fieldExpr.evaluateUnsafe(context);
+ if (fieldName == null) {
+ return null;
+ }
+ return context.currentMessage().getField(fieldName.toString());
+ }
+
+ @Override
+ public Class getType() {
+ return Object.class;
+ }
+
+ @Override
+ public String toString() {
+ return "$message." + fieldExpr.toString();
+ }
+
+ public Expression getFieldExpr() {
+ return fieldExpr;
+ }
+
+ @Override
+ public Iterable children() {
+ return Collections.singleton(fieldExpr);
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/MultiplicationExpression.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/MultiplicationExpression.java
new file mode 100644
index 000000000000..c055a152052d
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/MultiplicationExpression.java
@@ -0,0 +1,104 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import org.antlr.v4.runtime.Token;
+import org.graylog.plugins.pipelineprocessor.EvaluationContext;
+
+import javax.annotation.Nullable;
+
+import static com.google.common.base.MoreObjects.firstNonNull;
+
+public class MultiplicationExpression extends BinaryExpression implements NumericExpression {
+ private final char operator;
+ private Class type;
+
+ public MultiplicationExpression(Token start, Expression left, Expression right, char operator) {
+ super(start, left, right);
+ this.operator = operator;
+ }
+
+ @Override
+ public boolean isIntegral() {
+ return getType().equals(Long.class);
+ }
+
+ @Override
+ public long evaluateLong(EvaluationContext context) {
+ return (long) firstNonNull(evaluateUnsafe(context), 0);
+ }
+
+ @Override
+ public double evaluateDouble(EvaluationContext context) {
+ return (double) firstNonNull(evaluateUnsafe(context), 0d);
+ }
+
+ @SuppressWarnings("Duplicates")
+ @Nullable
+ @Override
+ public Object evaluateUnsafe(EvaluationContext context) {
+ final Object leftValue = left.evaluateUnsafe(context);
+ final Object rightValue = right.evaluateUnsafe(context);
+
+ if (isIntegral()) {
+ long l = (long) leftValue;
+ long r = (long) rightValue;
+ switch (operator) {
+ case '*':
+ return l * r;
+ case '/':
+ return l / r;
+ case '%':
+ return l % r;
+ default:
+ throw new IllegalStateException("Invalid operator, this is a bug.");
+ }
+ } else {
+ final double l = (double) leftValue;
+ final double r = (double) rightValue;
+
+ switch (operator) {
+ case '*':
+ return l * r;
+ case '/':
+ return l / r;
+ case '%':
+ return l % r;
+ default:
+ throw new IllegalStateException("Invalid operator, this is a bug.");
+ }
+ }
+ }
+
+ public char getOperator() {
+ return operator;
+ }
+
+ @Override
+ public Class getType() {
+ return type;
+ }
+
+ public void setType(Class type) {
+ this.type = type;
+ }
+
+ @Override
+ public String toString() {
+ return left.toString() + " " + operator + " " + right.toString();
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/NotExpression.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/NotExpression.java
new file mode 100644
index 000000000000..d799a8839905
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/NotExpression.java
@@ -0,0 +1,46 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import org.antlr.v4.runtime.Token;
+import org.graylog.plugins.pipelineprocessor.EvaluationContext;
+
+public class NotExpression extends UnaryExpression implements LogicalExpression {
+ public NotExpression(Token start, Expression right) {
+ super(start, right);
+ }
+
+ @Override
+ public Object evaluateUnsafe(EvaluationContext context) {
+ return evaluateBool(context);
+ }
+
+ @Override
+ public boolean evaluateBool(EvaluationContext context) {
+ return !((LogicalExpression)right).evaluateBool(context);
+ }
+
+ @Override
+ public Class getType() {
+ return Boolean.class;
+ }
+
+ @Override
+ public String toString() {
+ return "NOT " + right.toString();
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/NumericExpression.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/NumericExpression.java
new file mode 100644
index 000000000000..18200704210c
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/NumericExpression.java
@@ -0,0 +1,28 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import org.graylog.plugins.pipelineprocessor.EvaluationContext;
+
+public interface NumericExpression extends Expression {
+
+ boolean isIntegral();
+
+ long evaluateLong(EvaluationContext context);
+
+ double evaluateDouble(EvaluationContext context);
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/OrExpression.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/OrExpression.java
new file mode 100644
index 000000000000..87922fd05501
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/OrExpression.java
@@ -0,0 +1,47 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import org.antlr.v4.runtime.Token;
+import org.graylog.plugins.pipelineprocessor.EvaluationContext;
+
+public class OrExpression extends BinaryExpression implements LogicalExpression {
+ public OrExpression(Token start, Expression left,
+ Expression right) {
+ super(start, left, right);
+ }
+
+ @Override
+ public Object evaluateUnsafe(EvaluationContext context) {
+ return evaluateBool(context);
+ }
+
+ @Override
+ public boolean evaluateBool(EvaluationContext context) {
+ return ((LogicalExpression)left).evaluateBool(context) || ((LogicalExpression)right).evaluateBool(context);
+ }
+
+ @Override
+ public Class getType() {
+ return Boolean.class;
+ }
+
+ @Override
+ public String toString() {
+ return left.toString() + " OR " + right.toString();
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/SignedExpression.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/SignedExpression.java
new file mode 100644
index 000000000000..247fed85223f
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/SignedExpression.java
@@ -0,0 +1,73 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import org.antlr.v4.runtime.Token;
+import org.graylog.plugins.pipelineprocessor.EvaluationContext;
+
+import javax.annotation.Nullable;
+
+import static com.google.common.base.MoreObjects.firstNonNull;
+
+public class SignedExpression extends UnaryExpression implements NumericExpression {
+ private final boolean isPlus;
+
+ public SignedExpression(Token start, Expression right, boolean isPlus) {
+ super(start, right);
+ this.isPlus = isPlus;
+ }
+
+ @Override
+ public boolean isIntegral() {
+ return getType().equals(Long.class);
+ }
+
+ @Override
+ public long evaluateLong(EvaluationContext context) {
+ return (long) firstNonNull(evaluateUnsafe(context), 0);
+ }
+
+ @Override
+ public double evaluateDouble(EvaluationContext context) {
+ return (double) firstNonNull(evaluateUnsafe(context), 0d);
+ }
+
+ @Nullable
+ @Override
+ public Object evaluateUnsafe(EvaluationContext context) {
+ final Object value = right.evaluateUnsafe(context);
+
+ if (value instanceof Long) {
+ long number = (long) value;
+ return isPlus ? +number : -number;
+ } else if (value instanceof Double) {
+ double number = (double) value;
+ return isPlus ? +number : -number;
+ }
+ // nothing we could handle, the type checker should've caught it
+ throw new IllegalArgumentException("Value of '" + right.toString() + "' is not a number: " + value);
+ }
+
+ @Override
+ public String toString() {
+ return (isPlus ? " + " : " - ") + right.toString();
+ }
+
+ public boolean isPlus() {
+ return isPlus;
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/StringExpression.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/StringExpression.java
new file mode 100644
index 000000000000..d648ee16b62c
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/StringExpression.java
@@ -0,0 +1,40 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import org.antlr.v4.runtime.Token;
+import org.graylog.plugins.pipelineprocessor.EvaluationContext;
+
+public class StringExpression extends ConstantExpression {
+
+ private final String value;
+
+ public StringExpression(Token start, String value) {
+ super(start, String.class);
+ this.value = value;
+ }
+
+ @Override
+ public Object evaluateUnsafe(EvaluationContext context) {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return '"' + value + '"';
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/UnaryExpression.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/UnaryExpression.java
new file mode 100644
index 000000000000..5b008f88edbf
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/UnaryExpression.java
@@ -0,0 +1,68 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import org.antlr.v4.runtime.Token;
+import org.graylog.plugins.pipelineprocessor.parser.ParseException;
+import org.graylog.plugins.pipelineprocessor.parser.errors.SyntaxError;
+
+import java.util.Collections;
+
+public abstract class UnaryExpression extends BaseExpression {
+
+ protected Expression right;
+
+ public UnaryExpression(Token start, Expression right) {
+ super(start);
+ this.right = requireNonNull(right, start);
+ }
+
+ private static Expression requireNonNull(Expression expression, Token token) {
+ if (expression != null) {
+ return expression;
+ } else {
+ final int line = token.getLine();
+ final int positionInLine = token.getCharPositionInLine();
+ final String msg = "Invalid expression (line: " + line + ", column: " + positionInLine + ")";
+ final SyntaxError syntaxError = new SyntaxError(token.getText(), line, positionInLine, msg, null);
+ throw new ParseException(Collections.singleton(syntaxError));
+ }
+ }
+
+ @Override
+ public boolean isConstant() {
+ return right.isConstant();
+ }
+
+ @Override
+ public Class getType() {
+ return right.getType();
+ }
+
+ public Expression right() {
+ return right;
+ }
+
+ public void right(Expression right) {
+ this.right = right;
+ }
+
+ @Override
+ public Iterable children() {
+ return Collections.singleton(right);
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/VarRefExpression.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/VarRefExpression.java
new file mode 100644
index 000000000000..47497ab08149
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/expressions/VarRefExpression.java
@@ -0,0 +1,77 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.expressions;
+
+import org.antlr.v4.runtime.Token;
+import org.graylog.plugins.pipelineprocessor.EvaluationContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+
+public class VarRefExpression extends BaseExpression {
+ private static final Logger log = LoggerFactory.getLogger(VarRefExpression.class);
+ private final String identifier;
+ private final Expression varExpr;
+ private Class type = Object.class;
+
+ public VarRefExpression(Token start, String identifier, Expression varExpr) {
+ super(start);
+ this.identifier = identifier;
+ this.varExpr = varExpr;
+ }
+
+ @Override
+ public boolean isConstant() {
+ return varExpr != null && varExpr.isConstant();
+ }
+
+ @Override
+ public Object evaluateUnsafe(EvaluationContext context) {
+ final EvaluationContext.TypedValue typedValue = context.get(identifier);
+ if (typedValue != null) {
+ return typedValue.getValue();
+ }
+ log.error("Unable to retrieve value for variable {}", identifier);
+ return null;
+ }
+
+ @Override
+ public Class getType() {
+ return type;
+ }
+
+ @Override
+ public String toString() {
+ return identifier;
+ }
+
+ public String varName() {
+ return identifier;
+ }
+
+ public Expression varExpr() { return varExpr; }
+
+ public void setType(Class type) {
+ this.type = type;
+ }
+
+ @Override
+ public Iterable children() {
+ return Collections.emptySet();
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/functions/AbstractFunction.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/functions/AbstractFunction.java
new file mode 100644
index 000000000000..6a45249aef38
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/functions/AbstractFunction.java
@@ -0,0 +1,33 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.functions;
+
+import org.graylog.plugins.pipelineprocessor.EvaluationContext;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.Expression;
+
+/**
+ * Helper Function implementation which evaluates and memoizes all constant FunctionArgs.
+ *
+ * @param the return type
+ */
+public abstract class AbstractFunction implements Function {
+
+ @Override
+ public Object preComputeConstantArgument(FunctionArgs args, String name, Expression arg) {
+ return arg.evaluateUnsafe(EvaluationContext.emptyContext());
+ }
+}
diff --git a/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/functions/Function.java b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/functions/Function.java
new file mode 100644
index 000000000000..c38abeae63fb
--- /dev/null
+++ b/graylog2-server/src/main/java/org/graylog/plugins/pipelineprocessor/ast/functions/Function.java
@@ -0,0 +1,90 @@
+/**
+ * This file is part of Graylog.
+ *
+ * Graylog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Graylog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Graylog. If not, see .
+ */
+package org.graylog.plugins.pipelineprocessor.ast.functions;
+
+import com.google.common.collect.ImmutableList;
+import org.graylog.plugins.pipelineprocessor.EvaluationContext;
+import org.graylog.plugins.pipelineprocessor.ast.exceptions.PrecomputeFailure;
+import org.graylog.plugins.pipelineprocessor.ast.expressions.Expression;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+
+public interface Function {
+
+ Logger log = LoggerFactory.getLogger(Function.class);
+
+ Function ERROR_FUNCTION = new AbstractFunction() {
+ @Override
+ public Void evaluate(FunctionArgs args, EvaluationContext context) {
+ return null;
+ }
+
+ @Override
+ public void preprocessArgs(FunctionArgs args) {
+ // intentionally left blank
+ }
+
+ @Override
+ public FunctionDescriptor descriptor() {
+ return FunctionDescriptor.builder()
+ .name("__unresolved_function")
+ .returnType(Void.class)
+ .params(ImmutableList.of())
+ .build();
+ }
+ };
+
+ default void preprocessArgs(FunctionArgs args) {
+ for (Map.Entry e : args.getConstantArgs().entrySet()) {
+ final String name = e.getKey();
+ try {
+ final Object value = preComputeConstantArgument(args, name, e.getValue());
+ if (value != null) {
+ //noinspection unchecked
+ final ParameterDescriptor