diff --git a/infra/axiom/pom.xml b/infra/axiom/pom.xml
new file mode 100644
index 00000000000..8a297e0e81b
--- /dev/null
+++ b/infra/axiom/pom.xml
@@ -0,0 +1,81 @@
+
+
+
+
+ 4.0.0
+
+
+ infra
+ com.evolveum.midpoint.infra
+ 4.2-SNAPSHOT
+
+
+ com.evolveum.axiom
+ axiom
+
+ Axiom
+
+
+ true
+
+
+
+
+ org.jetbrains
+ annotations
+
+
+ com.google.guava
+ guava
+
+
+ org.antlr
+ antlr4-runtime
+ 4.8-1
+
+
+
+ org.testng
+ testng
+ test
+
+
+ com.evolveum.midpoint.tools
+ test-ng
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+
+
+
+
+
+ org.antlr
+ antlr4-maven-plugin
+ 4.8-1
+
+
+
+ antlr4
+
+
+
+
+ true
+ true
+
+
+
+
+
diff --git a/infra/axiom/src/main/antlr4/com/evolveum/axiom/lang/antlr/Axiom.g4 b/infra/axiom/src/main/antlr4/com/evolveum/axiom/lang/antlr/Axiom.g4
new file mode 100644
index 00000000000..bee08241596
--- /dev/null
+++ b/infra/axiom/src/main/antlr4/com/evolveum/axiom/lang/antlr/Axiom.g4
@@ -0,0 +1,45 @@
+grammar Axiom;
+
+SEMICOLON : ';';
+LEFT_BRACE : '{';
+RIGHT_BRACE : '}';
+COLON : ':';
+PLUS : '+';
+LINE_COMMENT : [ \n\r\t]* ('//' (~[\r\n]*)) [ \n\r\t]* -> skip;
+SEP: [ \n\r\t]+;
+IDENTIFIER : [a-zA-Z_/][a-zA-Z0-9_\-./]*;
+
+fragment SQOUTE : '\'';
+fragment DQOUTE : '"';
+
+
+//fragment SUB_STRING : ('"' (ESC | ~["])*? '"') | ('\'' (ESC | ~['])* '\'');
+//fragment ESC : '\\' (["\\/bfnrt] | UNICODE);
+//fragment UNICODE : 'u' HEX HEX HEX HEX;
+//fragment HEX : [0-9a-fA-F] ;
+//STRING: ((~( '\r' | '\n' | '\t' | ' ' | ';' | '{' | '"' | '\'' | '}' | '/' | '+')~( '\r' | '\n' | '\t' | ' ' | ';' | '{' | '}' )* ) | SUB_STRING );
+
+fragment ESC : '\\';
+
+STRING_SINGLEQUOTE: SQOUTE ((ESC SQOUTE) | ~[\n'])* SQOUTE;
+STRING_DOUBLEQUOTE: DQOUTE ((ESC DQOUTE) | ~[\n"])* DQOUTE;
+//STRING_MULTILINE: '"""' (ESC | ~('"""'))* '"""';
+
+statement : SEP* identifier SEP* (argument)? SEP* (SEMICOLON | LEFT_BRACE SEP* (statement)* SEP* RIGHT_BRACE SEP*) SEP*;
+
+identifier : (prefix COLON)? localIdentifier;
+prefix : IDENTIFIER;
+localIdentifier : IDENTIFIER;
+
+
+// argument : STRING (SEP* PLUS SEP* STRING)* | IDENTIFIER;
+argument : identifier | string;
+string : singleQuoteString | doubleQuoteString | multilineString;
+
+
+singleQuoteString : STRING_SINGLEQUOTE;
+doubleQuoteString : STRING_DOUBLEQUOTE;
+multilineString: '"""\n' (~('"""'))*'"""';
+
+
+
diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/api/AxiomIdentifier.java b/infra/axiom/src/main/java/com/evolveum/axiom/api/AxiomIdentifier.java
new file mode 100644
index 00000000000..8af5ae6caba
--- /dev/null
+++ b/infra/axiom/src/main/java/com/evolveum/axiom/api/AxiomIdentifier.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2020 Evolveum and contributors
+ *
+ * This work is dual-licensed under the Apache License 2.0
+ * and European Union Public License. See LICENSE file for details.
+ */
+package com.evolveum.axiom.api;
+
+import java.util.Objects;
+
+import com.google.common.base.Preconditions;
+
+public class AxiomIdentifier {
+
+ public static final String AXIOM_NAMESPACE = "https://ns.evolveum.com/axiom";
+ private final String namespace;
+ private final String localName;
+
+ public AxiomIdentifier(String namespace, String localName) {
+ this.namespace = Preconditions.checkNotNull(namespace, "namespace");
+ this.localName = Preconditions.checkNotNull(localName, "localName");
+ }
+
+ public String getNamespace() {
+ return namespace;
+ }
+
+ public String getLocalName() {
+ return localName;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((localName == null) ? 0 : localName.hashCode());
+ result = prime * result + ((namespace == null) ? 0 : namespace.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!(obj instanceof AxiomIdentifier))
+ return false;
+ AxiomIdentifier other = (AxiomIdentifier) obj;
+ if (localName == null) {
+ if (other.localName != null)
+ return false;
+ } else if (!localName.equals(other.localName))
+ return false;
+ if (namespace == null) {
+ if (other.namespace != null)
+ return false;
+ } else if (!namespace.equals(other.namespace))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return localName;
+ }
+
+ public static AxiomIdentifier axiom(String identifier) {
+ return new AxiomIdentifier(AXIOM_NAMESPACE, identifier);
+ }
+
+ public static AxiomIdentifier from(String namespace, String localName) {
+ return new AxiomIdentifier(namespace, localName);
+ }
+
+}
diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/concepts/Lazy.java b/infra/axiom/src/main/java/com/evolveum/axiom/concepts/Lazy.java
new file mode 100644
index 00000000000..4f97b099b4a
--- /dev/null
+++ b/infra/axiom/src/main/java/com/evolveum/axiom/concepts/Lazy.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2020 Evolveum and contributors
+ *
+ * This work is dual-licensed under the Apache License 2.0
+ * and European Union Public License. See LICENSE file for details.
+ */
+package com.evolveum.axiom.concepts;
+
+public class Lazy implements java.util.function.Supplier {
+
+ private static final Lazy NULL = Lazy.instant(null);
+ private Object value;
+
+ private Lazy(Object supplier) {
+ value = supplier;
+ }
+
+ public static final Lazy from(Supplier extends T> supplier) {
+ return new Lazy<>(supplier);
+ }
+
+
+ public static Lazy instant(T value) {
+ return new Lazy(value);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static Lazy nullValue() {
+ return NULL;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T get() {
+ if(value instanceof Supplier>) {
+ value = ((Supplier>) value).get();
+ }
+ return (T) value;
+ }
+
+ public interface Supplier extends java.util.function.Supplier {
+
+ }
+
+}
diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/concepts/Optionals.java b/infra/axiom/src/main/java/com/evolveum/axiom/concepts/Optionals.java
new file mode 100644
index 00000000000..192d289544c
--- /dev/null
+++ b/infra/axiom/src/main/java/com/evolveum/axiom/concepts/Optionals.java
@@ -0,0 +1,16 @@
+package com.evolveum.axiom.concepts;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Optional;
+
+public class Optionals {
+
+ public static Optionalfirst(Collection collection) {
+ if(collection.isEmpty()) {
+ return Optional.empty();
+ }
+ return Optional.ofNullable(collection.iterator().next());
+ }
+
+}
diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomBaseDefinition.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomBaseDefinition.java
new file mode 100644
index 00000000000..628f31b3ec4
--- /dev/null
+++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomBaseDefinition.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2020 Evolveum and contributors
+ *
+ * This work is dual-licensed under the Apache License 2.0
+ * and European Union Public License. See LICENSE file for details.
+ */
+package com.evolveum.axiom.lang.api;
+
+import com.evolveum.axiom.api.AxiomIdentifier;
+
+public interface AxiomBaseDefinition {
+
+ AxiomIdentifier name();
+ String documentation();
+}
diff --git a/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomBuiltIn.java b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomBuiltIn.java
new file mode 100644
index 00000000000..f76845e020a
--- /dev/null
+++ b/infra/axiom/src/main/java/com/evolveum/axiom/lang/api/AxiomBuiltIn.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (c) 2020 Evolveum and contributors
+ *
+ * This work is dual-licensed under the Apache License 2.0
+ * and European Union Public License. See LICENSE file for details.
+ */
+package com.evolveum.axiom.lang.api;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+
+import com.evolveum.axiom.api.AxiomIdentifier;
+import com.evolveum.axiom.concepts.Lazy;
+import com.evolveum.axiom.concepts.Lazy.Supplier;
+import com.evolveum.axiom.lang.api.stmt.AxiomStatement;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMap.Builder;
+import com.google.common.collect.ImmutableSet;
+
+public class AxiomBuiltIn {
+
+ public static final Lazy