keycloak-aplcache

Merge pull request #311 from stianst/master Added JPA and

4/2/2014 10:05:07 AM

Changes

audit/api/src/main/java/org/keycloak/audit/AuditLoader.java 31(+0 -31)

audit/jboss-logging/src/main/resources/META-INF/services/org.keycloak.audit.AuditListener 1(+0 -1)

audit/jpa/pom.xml 32(+32 -0)

audit/mongo/pom.xml 129(+129 -0)

audit/pom.xml 2(+2 -0)

audit/tests/pom.xml 35(+35 -0)

server/pom.xml 20(+20 -0)

Details

diff --git a/audit/api/src/main/java/org/keycloak/audit/Audit.java b/audit/api/src/main/java/org/keycloak/audit/Audit.java
index ad23701..682ceb4 100644
--- a/audit/api/src/main/java/org/keycloak/audit/Audit.java
+++ b/audit/api/src/main/java/org/keycloak/audit/Audit.java
@@ -4,6 +4,7 @@ import org.jboss.logging.Logger;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.provider.ProviderFactoryLoader;
 
 import java.util.HashMap;
 import java.util.LinkedList;
@@ -20,14 +21,17 @@ public class Audit {
     private Event event;
 
     public static Audit create(RealmModel realm, String ipAddress) {
+        ProviderFactoryLoader<AuditListenerFactory> loader = ProviderFactoryLoader.load(AuditListenerFactory.class);
+
         List<AuditListener> listeners = null;
         if (realm.getAuditListeners() != null) {
             listeners = new LinkedList<AuditListener>();
 
             for (String id : realm.getAuditListeners()) {
-                listeners.add(AuditLoader.load(id));
+                listeners.add(loader.find(id).create());
             }
         }
+
         return new Audit(listeners, new Event()).realm(realm).ipAddress(ipAddress);
     }
 
diff --git a/audit/api/src/main/java/org/keycloak/audit/AuditListener.java b/audit/api/src/main/java/org/keycloak/audit/AuditListener.java
index e521300..1425379 100644
--- a/audit/api/src/main/java/org/keycloak/audit/AuditListener.java
+++ b/audit/api/src/main/java/org/keycloak/audit/AuditListener.java
@@ -1,11 +1,11 @@
 package org.keycloak.audit;
 
+import org.keycloak.provider.Provider;
+
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public interface AuditListener {
-
-    public String getId();
+public interface AuditListener extends Provider {
 
     public void onEvent(Event event);
 
diff --git a/audit/api/src/main/java/org/keycloak/audit/AuditListenerFactory.java b/audit/api/src/main/java/org/keycloak/audit/AuditListenerFactory.java
new file mode 100644
index 0000000..6ffeb20
--- /dev/null
+++ b/audit/api/src/main/java/org/keycloak/audit/AuditListenerFactory.java
@@ -0,0 +1,10 @@
+package org.keycloak.audit;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface AuditListenerFactory extends ProviderFactory<AuditListener> {
+
+}
diff --git a/audit/api/src/main/java/org/keycloak/audit/AuditProvider.java b/audit/api/src/main/java/org/keycloak/audit/AuditProvider.java
index 9cc2c0e..738b79f 100644
--- a/audit/api/src/main/java/org/keycloak/audit/AuditProvider.java
+++ b/audit/api/src/main/java/org/keycloak/audit/AuditProvider.java
@@ -7,4 +7,8 @@ public interface AuditProvider extends AuditListener {
 
     public EventQuery createQuery();
 
+    public void clear();
+
+    public void clear(long olderThan);
+
 }
diff --git a/audit/api/src/main/java/org/keycloak/audit/AuditProviderFactory.java b/audit/api/src/main/java/org/keycloak/audit/AuditProviderFactory.java
new file mode 100644
index 0000000..8d6331e
--- /dev/null
+++ b/audit/api/src/main/java/org/keycloak/audit/AuditProviderFactory.java
@@ -0,0 +1,10 @@
+package org.keycloak.audit;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface AuditProviderFactory extends ProviderFactory<AuditProvider> {
+
+}
diff --git a/audit/jboss-logging/pom.xml b/audit/jboss-logging/pom.xml
index f07d231..f3f11ba 100755
--- a/audit/jboss-logging/pom.xml
+++ b/audit/jboss-logging/pom.xml
@@ -19,6 +19,12 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-audit-api</artifactId>
             <version>${project.version}</version>
             <scope>provided</scope>
diff --git a/audit/jboss-logging/src/main/java/org/keycloak/audit/log/JBossLoggingAuditListener.java b/audit/jboss-logging/src/main/java/org/keycloak/audit/log/JBossLoggingAuditListener.java
index bec3b9d..d5381e2 100644
--- a/audit/jboss-logging/src/main/java/org/keycloak/audit/log/JBossLoggingAuditListener.java
+++ b/audit/jboss-logging/src/main/java/org/keycloak/audit/log/JBossLoggingAuditListener.java
@@ -11,11 +11,10 @@ import java.util.Map;
  */
 public class JBossLoggingAuditListener implements AuditListener {
 
-    private static final Logger logger = Logger.getLogger("org.keycloak.audit");
+    private final Logger logger;
 
-    @Override
-    public String getId() {
-        return "jboss-logging";
+    public JBossLoggingAuditListener(Logger logger) {
+        this.logger = logger;
     }
 
     @Override
@@ -60,4 +59,8 @@ public class JBossLoggingAuditListener implements AuditListener {
         }
     }
 
+    @Override
+    public void close() {
+    }
+
 }
diff --git a/audit/jboss-logging/src/main/java/org/keycloak/audit/log/JBossLoggingAuditListenerFactory.java b/audit/jboss-logging/src/main/java/org/keycloak/audit/log/JBossLoggingAuditListenerFactory.java
new file mode 100644
index 0000000..420bd67
--- /dev/null
+++ b/audit/jboss-logging/src/main/java/org/keycloak/audit/log/JBossLoggingAuditListenerFactory.java
@@ -0,0 +1,34 @@
+package org.keycloak.audit.log;
+
+import org.jboss.logging.Logger;
+import org.keycloak.audit.AuditListener;
+import org.keycloak.audit.AuditListenerFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class JBossLoggingAuditListenerFactory implements AuditListenerFactory {
+
+    public static final String ID = "jboss-logging";
+
+    private static final Logger logger = Logger.getLogger("org.keycloak.audit");
+
+    @Override
+    public AuditListener create() {
+        return new JBossLoggingAuditListener(logger);
+    }
+
+    @Override
+    public void init() {
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+}
diff --git a/audit/jboss-logging/src/main/resources/META-INF/services/org.keycloak.audit.AuditListenerFactory b/audit/jboss-logging/src/main/resources/META-INF/services/org.keycloak.audit.AuditListenerFactory
new file mode 100644
index 0000000..2370355
--- /dev/null
+++ b/audit/jboss-logging/src/main/resources/META-INF/services/org.keycloak.audit.AuditListenerFactory
@@ -0,0 +1 @@
+org.keycloak.audit.log.JBossLoggingAuditListenerFactory
\ No newline at end of file

audit/jpa/pom.xml 32(+32 -0)

diff --git a/audit/jpa/pom.xml b/audit/jpa/pom.xml
index 747bcc0..e8fa550 100755
--- a/audit/jpa/pom.xml
+++ b/audit/jpa/pom.xml
@@ -20,10 +20,42 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-audit-api</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-audit-tests</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.hibernate.javax.persistence</groupId>
+            <artifactId>hibernate-jpa-2.0-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.hibernate</groupId>
+            <artifactId>hibernate-entitymanager</artifactId>
+            <version>${hibernate.entitymanager.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>com.h2database</groupId>
+            <artifactId>h2</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.json</groupId>
+            <artifactId>json</artifactId>
+        </dependency>
    </dependencies>
 
 </project>
diff --git a/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/EventEntity.java b/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/EventEntity.java
new file mode 100644
index 0000000..a8fd531
--- /dev/null
+++ b/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/EventEntity.java
@@ -0,0 +1,103 @@
+package org.keycloak.audit.jpa;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+@Entity
+public class EventEntity {
+
+    @Id
+    private String id;
+
+    private long time;
+
+    private String event;
+
+    private String realmId;
+
+    private String clientId;
+
+    private String userId;
+
+    private String ipAddress;
+
+    private String error;
+
+    private String detailsJson;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public long getTime() {
+        return time;
+    }
+
+    public void setTime(long time) {
+        this.time = time;
+    }
+
+    public String getEvent() {
+        return event;
+    }
+
+    public void setEvent(String event) {
+        this.event = event;
+    }
+
+    public String getRealmId() {
+        return realmId;
+    }
+
+    public void setRealmId(String realmId) {
+        this.realmId = realmId;
+    }
+
+    public String getClientId() {
+        return clientId;
+    }
+
+    public void setClientId(String clientId) {
+        this.clientId = clientId;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getIpAddress() {
+        return ipAddress;
+    }
+
+    public void setIpAddress(String ipAddress) {
+        this.ipAddress = ipAddress;
+    }
+
+    public String getError() {
+        return error;
+    }
+
+    public void setError(String error) {
+        this.error = error;
+    }
+
+    public String getDetailsJson() {
+        return detailsJson;
+    }
+
+    public void setDetailsJson(String detailsJson) {
+        this.detailsJson = detailsJson;
+    }
+
+}
diff --git a/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/JpaAuditProvider.java b/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/JpaAuditProvider.java
new file mode 100644
index 0000000..0432f17
--- /dev/null
+++ b/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/JpaAuditProvider.java
@@ -0,0 +1,99 @@
+package org.keycloak.audit.jpa;
+
+import org.json.JSONObject;
+import org.keycloak.audit.AuditProvider;
+import org.keycloak.audit.Event;
+import org.keycloak.audit.EventQuery;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityTransaction;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class JpaAuditProvider implements AuditProvider {
+
+    private EntityManager em;
+    private EntityTransaction tx;
+
+    public JpaAuditProvider(EntityManager em) {
+        this.em = em;
+    }
+
+    @Override
+    public EventQuery createQuery() {
+        return new JpaEventQuery(em);
+    }
+
+    @Override
+    public void clear() {
+        beginTx();
+        em.createQuery("delete from EventEntity").executeUpdate();
+    }
+
+    @Override
+    public void clear(long olderThan) {
+        beginTx();
+        em.createQuery("delete from EventEntity where time < :time").setParameter("time", olderThan).executeUpdate();
+    }
+
+    @Override
+    public void onEvent(Event event) {
+        beginTx();
+        em.persist(convert(event));
+    }
+
+    @Override
+    public void close() {
+        if (tx != null) {
+            tx.commit();
+        }
+
+        em.close();
+    }
+
+    private void beginTx() {
+        if (tx == null) {
+            tx = em.getTransaction();
+            tx.begin();
+        }
+    }
+
+    static EventEntity convert(Event o) {
+        EventEntity e = new EventEntity();
+        e.setId(UUID.randomUUID().toString());
+        e.setTime(o.getTime());
+        e.setEvent(o.getEvent());
+        e.setRealmId(o.getRealmId());
+        e.setClientId(o.getClientId());
+        e.setUserId(o.getUserId());
+        e.setIpAddress(o.getIpAddress());
+        e.setError(o.getError());
+        e.setDetailsJson(new JSONObject(o.getDetails()).toString());
+        return e;
+    }
+
+    static Event convert(EventEntity o) {
+        Event e = new Event();
+        e.setTime(o.getTime());
+        e.setEvent(o.getEvent());
+        e.setRealmId(o.getRealmId());
+        e.setClientId(o.getClientId());
+        e.setUserId(o.getUserId());
+        e.setIpAddress(o.getIpAddress());
+        e.setError(o.getError());
+
+        JSONObject object = new JSONObject(o.getDetailsJson());
+        Map<String, String> details = new HashMap<String, String>();
+        for (Object k : object.keySet()) {
+            details.put((String) k, object.getString((String) k));
+        }
+
+        e.setDetails(details);
+        return e;
+    }
+
+}
diff --git a/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/JpaAuditProviderFactory.java b/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/JpaAuditProviderFactory.java
new file mode 100644
index 0000000..e05ab63
--- /dev/null
+++ b/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/JpaAuditProviderFactory.java
@@ -0,0 +1,37 @@
+package org.keycloak.audit.jpa;
+
+import org.keycloak.audit.AuditProvider;
+import org.keycloak.audit.AuditProviderFactory;
+
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class JpaAuditProviderFactory implements AuditProviderFactory {
+
+    public static final String ID = "jpa";
+    private EntityManagerFactory emf;
+
+    @Override
+    public AuditProvider create() {
+        return new JpaAuditProvider(emf.createEntityManager());
+    }
+
+    @Override
+    public void init() {
+        emf = Persistence.createEntityManagerFactory("jpa-keycloak-audit-store");
+    }
+
+    @Override
+    public void close() {
+        emf.close();
+    }
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+}
diff --git a/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/JpaEventQuery.java b/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/JpaEventQuery.java
new file mode 100644
index 0000000..f4ec884
--- /dev/null
+++ b/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/JpaEventQuery.java
@@ -0,0 +1,100 @@
+package org.keycloak.audit.jpa;
+
+import org.keycloak.audit.Event;
+import org.keycloak.audit.EventQuery;
+
+import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class JpaEventQuery implements EventQuery {
+
+    private final EntityManager em;
+    private final CriteriaBuilder cb;
+    private final CriteriaQuery<EventEntity> cq;
+    private final Root<EventEntity> root;
+    private final ArrayList<Predicate> predicates;
+    private Integer firstResult;
+    private Integer maxResults;
+
+    public JpaEventQuery(EntityManager em) {
+        this.em = em;
+
+        cb = em.getCriteriaBuilder();
+        cq = cb.createQuery(EventEntity.class);
+        root = cq.from(EventEntity.class);
+        predicates = new ArrayList<Predicate>(4);
+    }
+
+    @Override
+    public EventQuery event(String event) {
+        predicates.add(cb.equal(root.get("event"), event));
+        return this;
+    }
+
+    @Override
+    public EventQuery realm(String realmId) {
+        predicates.add(cb.equal(root.get("realmId"), realmId));
+        return this;
+    }
+
+    @Override
+    public EventQuery client(String clientId) {
+        predicates.add(cb.equal(root.get("clientId"), clientId));
+        return this;
+    }
+
+    @Override
+    public EventQuery user(String userId) {
+        predicates.add(cb.equal(root.get("userId"), userId));
+        return this;
+    }
+
+    @Override
+    public EventQuery firstResult(int firstResult) {
+        this.firstResult = firstResult;
+        return this;
+    }
+
+    @Override
+    public EventQuery maxResults(int maxResults) {
+        this.maxResults = maxResults;
+        return this;
+    }
+
+    @Override
+    public List<Event> getResultList() {
+        if (!predicates.isEmpty()) {
+            cq.where(cb.and(predicates.toArray(new Predicate[predicates.size()])));
+        }
+
+        cq.orderBy(cb.asc(root.get("time")), cb.asc(root.get("id")));
+
+        TypedQuery<EventEntity> query = em.createQuery(cq);
+
+        if (firstResult != null) {
+            query.setFirstResult(firstResult);
+        }
+
+        if (maxResults != null) {
+            query.setMaxResults(maxResults);
+        }
+
+        List<Event> events = new LinkedList<Event>();
+        for (EventEntity e : query.getResultList()) {
+            events.add(JpaAuditProvider.convert(e));
+        }
+
+        return events;
+    }
+
+}
diff --git a/audit/jpa/src/src/main/resources/META-INF/services/org.keycloak.audit.AuditProviderFactory b/audit/jpa/src/src/main/resources/META-INF/services/org.keycloak.audit.AuditProviderFactory
new file mode 100644
index 0000000..e04a028
--- /dev/null
+++ b/audit/jpa/src/src/main/resources/META-INF/services/org.keycloak.audit.AuditProviderFactory
@@ -0,0 +1 @@
+org.keycloak.audit.jpa.JpaAuditProviderFactory
\ No newline at end of file
diff --git a/audit/jpa/src/src/test/java/org/keycloak/audit/jpa/JpaAuditProviderTest.java b/audit/jpa/src/src/test/java/org/keycloak/audit/jpa/JpaAuditProviderTest.java
new file mode 100644
index 0000000..2cabb9c
--- /dev/null
+++ b/audit/jpa/src/src/test/java/org/keycloak/audit/jpa/JpaAuditProviderTest.java
@@ -0,0 +1,15 @@
+package org.keycloak.audit.jpa;
+
+import org.keycloak.audit.tests.AbstractAuditProviderTest;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class JpaAuditProviderTest extends AbstractAuditProviderTest {
+
+    @Override
+    protected String getProviderId() {
+        return JpaAuditProviderFactory.ID;
+    }
+
+}
diff --git a/audit/jpa/src/src/test/resources/META-INF/persistence.xml b/audit/jpa/src/src/test/resources/META-INF/persistence.xml
new file mode 100755
index 0000000..f537808
--- /dev/null
+++ b/audit/jpa/src/src/test/resources/META-INF/persistence.xml
@@ -0,0 +1,22 @@
+<persistence xmlns="http://java.sun.com/xml/ns/persistence"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
+    version="1.0">
+    <persistence-unit name="jpa-keycloak-audit-store" transaction-type="RESOURCE_LOCAL">
+        <provider>org.hibernate.ejb.HibernatePersistence</provider>
+
+        <class>org.keycloak.audit.jpa.EventEntity</class>
+
+        <exclude-unlisted-classes>true</exclude-unlisted-classes>
+
+        <properties>
+            <property name="hibernate.connection.url" value="jdbc:h2:mem:test"/>
+            <property name="hibernate.connection.driver_class" value="org.h2.Driver"/>
+            <property name="hibernate.connection.username" value="sa"/>
+            <property name="hibernate.connection.password" value=""/>
+            <property name="hibernate.hbm2ddl.auto" value="create-drop" />
+            <property name="hibernate.show_sql" value="false" />
+            <property name="hibernate.format_sql" value="true" />
+        </properties>
+    </persistence-unit>
+</persistence>

audit/mongo/pom.xml 129(+129 -0)

diff --git a/audit/mongo/pom.xml b/audit/mongo/pom.xml
new file mode 100755
index 0000000..53f6f73
--- /dev/null
+++ b/audit/mongo/pom.xml
@@ -0,0 +1,129 @@
+<?xml version="1.0"?>
+<project>
+    <parent>
+        <artifactId>keycloak-audit-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.0-beta-1-SNAPSHOT</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-audit-mongo</artifactId>
+    <name>Keycloak Audit Mongo Provider</name>
+    <description/>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-audit-api</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-audit-tests</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mongodb</groupId>
+            <artifactId>mongo-java-driver</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.json</groupId>
+            <artifactId>json</artifactId>
+        </dependency>
+   </dependencies>
+
+    <properties>
+        <keycloak.audit.mongo.host>localhost</keycloak.audit.mongo.host>
+        <keycloak.audit.mongo.port>27018</keycloak.audit.mongo.port>
+        <keycloak.audit.mongo.db>keycloak</keycloak.audit.mongo.db>
+        <keycloak.audit.mongo.clearOnStartup>true</keycloak.audit.mongo.clearOnStartup>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.6</source>
+                    <target>1.6</target>
+                </configuration>
+            </plugin>
+
+            <!-- Postpone tests to "integration-test" phase, so that we can bootstrap embedded mongo on 27018 before running tests -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>test</id>
+                        <phase>integration-test</phase>
+                        <goals>
+                            <goal>test</goal>
+                        </goals>
+                        <configuration>
+                            <systemPropertyVariables>
+                                <keycloak.audit.mongo.host>${keycloak.audit.mongo.host}</keycloak.audit.mongo.host>
+                                <keycloak.audit.mongo.port>${keycloak.audit.mongo.port}</keycloak.audit.mongo.port>
+                                <keycloak.audit.mongo.db>${keycloak.audit.mongo.db}</keycloak.audit.mongo.db>
+                                <keycloak.audit.mongo.clearOnStartup>${keycloak.audit.mongo.clearOnStartup}</keycloak.audit.mongo.clearOnStartup>
+                            </systemPropertyVariables>
+                            <dependenciesToScan>
+                                <dependency>org.keycloak:keycloak-model-tests</dependency>
+                            </dependenciesToScan>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>default-test</id>
+                        <configuration>
+                            <skip>true</skip>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <!-- Embedded mongo -->
+            <plugin>
+                <groupId>com.github.joelittlejohn.embedmongo</groupId>
+                <artifactId>embedmongo-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>start-mongodb</id>
+                        <phase>pre-integration-test</phase>
+                        <goals>
+                            <goal>start</goal>
+                        </goals>
+                        <configuration>
+                            <port>${keycloak.audit.mongo.port}</port>
+                            <logging>file</logging>
+                            <logFile>${project.build.directory}/mongodb.log</logFile>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>stop-mongodb</id>
+                        <phase>post-integration-test</phase>
+                        <goals>
+                            <goal>stop</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+        </plugins>
+    </build>
+</project>
diff --git a/audit/mongo/src/main/java/org/keycloak/audit/mongo/MongoAuditProvider.java b/audit/mongo/src/main/java/org/keycloak/audit/mongo/MongoAuditProvider.java
new file mode 100644
index 0000000..6958652
--- /dev/null
+++ b/audit/mongo/src/main/java/org/keycloak/audit/mongo/MongoAuditProvider.java
@@ -0,0 +1,87 @@
+package org.keycloak.audit.mongo;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.DBCollection;
+import com.mongodb.DBObject;
+import org.keycloak.audit.AuditProvider;
+import org.keycloak.audit.Event;
+import org.keycloak.audit.EventQuery;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class MongoAuditProvider implements AuditProvider {
+
+    private DBCollection audit;
+
+    public MongoAuditProvider(DBCollection audit) {
+        this.audit = audit;
+    }
+
+    @Override
+    public EventQuery createQuery() {
+        return new MongoEventQuery(audit);
+    }
+
+    @Override
+    public void clear() {
+        audit.remove(new BasicDBObject());
+    }
+
+    @Override
+    public void clear(long olderThan) {
+        audit.remove(new BasicDBObject("time", new BasicDBObject("$lt", olderThan)));
+    }
+
+    @Override
+    public void onEvent(Event event) {
+        audit.insert(convert(event));
+    }
+
+    @Override
+    public void close() {
+    }
+
+    static DBObject convert(Event o) {
+        BasicDBObject e = new BasicDBObject();
+        e.put("time", o.getTime());
+        e.put("event", o.getEvent());
+        e.put("realmId", o.getRealmId());
+        e.put("clientId", o.getClientId());
+        e.put("userId", o.getUserId());
+        e.put("ipAddress", o.getIpAddress());
+        e.put("error", o.getError());
+
+        BasicDBObject details = new BasicDBObject();
+        for (Map.Entry<String, String> entry : o.getDetails().entrySet())  {
+            details.put(entry.getKey(), entry.getValue());
+        }
+        e.put("details", details);
+
+        return e;
+    }
+
+    static Event convert(BasicDBObject o) {
+        Event e = new Event();
+        e.setTime(o.getLong("time"));
+        e.setEvent(o.getString("event"));
+        e.setRealmId(o.getString("realmId"));
+        e.setClientId(o.getString("clientId"));
+        e.setUserId(o.getString("userId"));
+        e.setIpAddress(o.getString("ipAddress"));
+        e.setError(o.getString("error"));
+
+        BasicDBObject d = (BasicDBObject) o.get("details");
+        Map<String, String> details = new HashMap<String, String>();
+        for (Object k : d.keySet()) {
+            details.put((String) k, d.getString((String) k));
+        }
+
+        e.setDetails(details);
+        return e;
+    }
+
+}
diff --git a/audit/mongo/src/main/java/org/keycloak/audit/mongo/MongoAuditProviderFactory.java b/audit/mongo/src/main/java/org/keycloak/audit/mongo/MongoAuditProviderFactory.java
new file mode 100644
index 0000000..5fb7a83
--- /dev/null
+++ b/audit/mongo/src/main/java/org/keycloak/audit/mongo/MongoAuditProviderFactory.java
@@ -0,0 +1,50 @@
+package org.keycloak.audit.mongo;
+
+import com.mongodb.DB;
+import com.mongodb.MongoClient;
+import com.mongodb.WriteConcern;
+import org.keycloak.audit.AuditProvider;
+import org.keycloak.audit.AuditProviderFactory;
+
+import java.net.UnknownHostException;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class MongoAuditProviderFactory implements AuditProviderFactory {
+
+    private static final String MONGO_HOST = "keycloak.audit.mongo.host";
+    private static final String MONGO_PORT = "keycloak.audit.mongo.port";
+    private static final String MONGO_DB_NAME = "keycloak.audit.mongo.db";
+
+    public static final String ID = "mongo";
+    private MongoClient client;
+    private DB db;
+
+    @Override
+    public AuditProvider create() {
+        return new MongoAuditProvider(db.getCollection("audit"));
+    }
+
+    @Override
+    public void init() {
+        try {
+            client = new MongoClient(System.getProperty(MONGO_HOST, "localhost"), Integer.parseInt(System.getProperty(MONGO_PORT, "27017")));
+            client.setWriteConcern(WriteConcern.UNACKNOWLEDGED);
+            db = client.getDB(System.getProperty(MONGO_DB_NAME, "keycloak-audit"));
+        } catch (UnknownHostException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void close() {
+        client.close();
+    }
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+}
diff --git a/audit/mongo/src/main/java/org/keycloak/audit/mongo/MongoEventQuery.java b/audit/mongo/src/main/java/org/keycloak/audit/mongo/MongoEventQuery.java
new file mode 100644
index 0000000..e425455
--- /dev/null
+++ b/audit/mongo/src/main/java/org/keycloak/audit/mongo/MongoEventQuery.java
@@ -0,0 +1,81 @@
+package org.keycloak.audit.mongo;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.DBCollection;
+import com.mongodb.DBCursor;
+import org.keycloak.audit.Event;
+import org.keycloak.audit.EventQuery;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class MongoEventQuery implements EventQuery {
+
+    private Integer firstResult;
+    private Integer maxResults;
+    private DBCollection audit;
+    private final BasicDBObject query;
+
+    public MongoEventQuery(DBCollection audit) {
+        this.audit = audit;
+        query = new BasicDBObject();
+    }
+
+    @Override
+    public EventQuery event(String event) {
+        query.put("event", event);
+        return this;
+    }
+
+    @Override
+    public EventQuery realm(String realmId) {
+        query.put("realmId", realmId);
+        return this;
+    }
+
+    @Override
+    public EventQuery client(String clientId) {
+        query.put("clientId", clientId);
+        return this;
+    }
+
+    @Override
+    public EventQuery user(String userId) {
+        query.put("userId", userId);
+        return this;
+    }
+
+    @Override
+    public EventQuery firstResult(int firstResult) {
+        this.firstResult = firstResult;
+        return this;
+    }
+
+    @Override
+    public EventQuery maxResults(int maxResults) {
+        this.maxResults = maxResults;
+        return this;
+    }
+
+    @Override
+    public List<Event> getResultList() {
+        DBCursor cur = audit.find(query);
+        if (firstResult != null) {
+            cur.skip(firstResult);
+        }
+        if (maxResults != null) {
+            cur.limit(maxResults);
+        }
+
+        List<Event> events = new LinkedList<Event>();
+        while (cur.hasNext()) {
+            events.add(MongoAuditProvider.convert((BasicDBObject) cur.next()));
+        }
+
+        return events;
+    }
+
+}
diff --git a/audit/mongo/src/main/resources/META-INF/services/org.keycloak.audit.AuditProviderFactory b/audit/mongo/src/main/resources/META-INF/services/org.keycloak.audit.AuditProviderFactory
new file mode 100644
index 0000000..ea4454d
--- /dev/null
+++ b/audit/mongo/src/main/resources/META-INF/services/org.keycloak.audit.AuditProviderFactory
@@ -0,0 +1 @@
+org.keycloak.audit.mongo.MongoAuditProviderFactory
\ No newline at end of file
diff --git a/audit/mongo/src/test/java/org/keycloak/audit/mongo/MongoAuditProviderTest.java b/audit/mongo/src/test/java/org/keycloak/audit/mongo/MongoAuditProviderTest.java
new file mode 100644
index 0000000..5ef91ad
--- /dev/null
+++ b/audit/mongo/src/test/java/org/keycloak/audit/mongo/MongoAuditProviderTest.java
@@ -0,0 +1,15 @@
+package org.keycloak.audit.mongo;
+
+import org.keycloak.audit.tests.AbstractAuditProviderTest;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class MongoAuditProviderTest extends AbstractAuditProviderTest {
+
+    @Override
+    protected String getProviderId() {
+        return MongoAuditProviderFactory.ID;
+    }
+
+}

audit/pom.xml 2(+2 -0)

diff --git a/audit/pom.xml b/audit/pom.xml
index 8ef4451..ed125d1 100755
--- a/audit/pom.xml
+++ b/audit/pom.xml
@@ -19,5 +19,7 @@
         <module>api</module>
         <module>jpa</module>
         <module>jboss-logging</module>
+        <module>mongo</module>
+        <module>tests</module>
     </modules>
 </project>

audit/tests/pom.xml 35(+35 -0)

diff --git a/audit/tests/pom.xml b/audit/tests/pom.xml
new file mode 100755
index 0000000..1b8cac1
--- /dev/null
+++ b/audit/tests/pom.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>keycloak-audit-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.0-beta-1-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-audit-tests</artifactId>
+    <name>Keycloak Audit Tests</name>
+    <description/>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-audit-api</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/audit/tests/src/main/java/org/keycloak/audit/tests/AbstractAuditProviderTest.java b/audit/tests/src/main/java/org/keycloak/audit/tests/AbstractAuditProviderTest.java
new file mode 100644
index 0000000..c5d1bb7
--- /dev/null
+++ b/audit/tests/src/main/java/org/keycloak/audit/tests/AbstractAuditProviderTest.java
@@ -0,0 +1,121 @@
+package org.keycloak.audit.tests;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.audit.AuditProvider;
+import org.keycloak.audit.AuditProviderFactory;
+import org.keycloak.audit.Event;
+import org.keycloak.provider.ProviderFactoryLoader;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public abstract class AbstractAuditProviderTest {
+
+    private AuditProviderFactory factory;
+    private AuditProvider provider;
+
+    @Before
+    public void before() {
+        ProviderFactoryLoader<AuditProviderFactory> loader = ProviderFactoryLoader.load(AuditProviderFactory.class);
+        factory = loader.find(getProviderId());
+        factory.init();
+
+        provider = factory.create();
+    }
+
+    protected abstract String getProviderId();
+
+    @After
+    public void after() {
+        provider.clear();
+        provider.close();
+        factory.close();
+    }
+
+    @Test
+    public void save() {
+        provider.onEvent(create("event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
+    }
+
+    @Test
+    public void query() {
+        provider.onEvent(create("event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
+        provider.onEvent(create("event2", "realmId", "clientId", "userId", "127.0.0.1", "error"));
+        provider.onEvent(create("event", "realmId2", "clientId", "userId", "127.0.0.1", "error"));
+        provider.onEvent(create("event", "realmId", "clientId2", "userId", "127.0.0.1", "error"));
+        provider.onEvent(create("event", "realmId", "clientId", "userId2", "127.0.0.1", "error"));
+
+        provider.close();
+        provider = factory.create();
+
+        Assert.assertEquals(4, provider.createQuery().client("clientId").getResultList().size());
+        Assert.assertEquals(4, provider.createQuery().realm("realmId").getResultList().size());
+        Assert.assertEquals(4, provider.createQuery().event("event").getResultList().size());
+        Assert.assertEquals(4, provider.createQuery().user("userId").getResultList().size());
+
+        Assert.assertEquals(1, provider.createQuery().user("userId").event("event2").getResultList().size());
+
+        Assert.assertEquals(2, provider.createQuery().maxResults(2).getResultList().size());
+        Assert.assertEquals(1, provider.createQuery().firstResult(4).getResultList().size());
+    }
+
+    @Test
+    public void clear() {
+        provider.onEvent(create(System.currentTimeMillis() - 30000, "event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
+        provider.onEvent(create(System.currentTimeMillis() - 20000, "event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
+        provider.onEvent(create(System.currentTimeMillis(), "event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
+        provider.onEvent(create(System.currentTimeMillis(), "event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
+
+        provider.close();
+        provider = factory.create();
+
+        provider.clear();
+
+        Assert.assertEquals(0, provider.createQuery().getResultList().size());
+    }
+
+    @Test
+    public void clearOld() {
+        provider.onEvent(create(System.currentTimeMillis() - 30000, "event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
+        provider.onEvent(create(System.currentTimeMillis() - 20000, "event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
+        provider.onEvent(create(System.currentTimeMillis(), "event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
+        provider.onEvent(create(System.currentTimeMillis(), "event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
+
+        provider.close();
+        provider = factory.create();
+
+        provider.clear(System.currentTimeMillis() - 10000);
+
+        Assert.assertEquals(2, provider.createQuery().getResultList().size());
+    }
+
+    private Event create(String event, String realmId, String clientId, String userId, String ipAddress, String error) {
+        return create(System.currentTimeMillis(), event, realmId, clientId, userId, ipAddress, error);
+    }
+
+    private Event create(long time, String event, String realmId, String clientId, String userId, String ipAddress, String error) {
+        Event e = new Event();
+        e.setTime(time);
+        e.setEvent(event);
+        e.setRealmId(realmId);
+        e.setClientId(clientId);
+        e.setUserId(userId);
+        e.setIpAddress(ipAddress);
+        e.setError(error);
+
+        Map<String, String> details = new HashMap<String, String>();
+        details.put("key1", "value1");
+        details.put("key2", "value2");
+
+        e.setDetails(details);
+
+        return e;
+    }
+
+}
diff --git a/core/src/main/java/org/keycloak/provider/Provider.java b/core/src/main/java/org/keycloak/provider/Provider.java
new file mode 100644
index 0000000..3401266
--- /dev/null
+++ b/core/src/main/java/org/keycloak/provider/Provider.java
@@ -0,0 +1,10 @@
+package org.keycloak.provider;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface Provider {
+
+    public void close();
+
+}
diff --git a/core/src/main/java/org/keycloak/provider/ProviderFactory.java b/core/src/main/java/org/keycloak/provider/ProviderFactory.java
new file mode 100644
index 0000000..720538d
--- /dev/null
+++ b/core/src/main/java/org/keycloak/provider/ProviderFactory.java
@@ -0,0 +1,16 @@
+package org.keycloak.provider;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface ProviderFactory<T extends Provider> {
+
+    public T create();
+
+    public void init();
+
+    public void close();
+
+    public String getId();
+
+}
diff --git a/core/src/main/java/org/keycloak/provider/ProviderFactoryLoader.java b/core/src/main/java/org/keycloak/provider/ProviderFactoryLoader.java
new file mode 100644
index 0000000..51b00dc
--- /dev/null
+++ b/core/src/main/java/org/keycloak/provider/ProviderFactoryLoader.java
@@ -0,0 +1,88 @@
+package org.keycloak.provider;
+
+import java.util.Iterator;
+import java.util.ServiceLoader;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ProviderFactoryLoader<P extends ProviderFactory> implements Iterable<P> {
+
+    private ServiceLoader<P> serviceLoader;
+
+    private ProviderFactoryLoader(ServiceLoader<P> serviceLoader) {
+        this.serviceLoader = serviceLoader;
+    }
+
+    public static <P extends ProviderFactory> ProviderFactoryLoader<P> load(Class<P> service) {
+        return new ProviderFactoryLoader(ServiceLoader.load(service));
+    }
+
+    public static <P extends ProviderFactory> ProviderFactoryLoader<P> load(Class<P> service, ClassLoader loader) {
+        return new ProviderFactoryLoader(ServiceLoader.load(service, loader));
+    }
+
+    public P find(String id) {
+        Iterator<P> itr = iterator();
+        while (itr.hasNext()) {
+            P p = itr.next();
+            if (p.getId() != null && p.getId().equals(id)) {
+                return p;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Iterator<P> iterator() {
+        return new ProviderFactoryIterator(serviceLoader.iterator());
+    }
+
+    public void close() {
+
+    }
+
+    private static class ProviderFactoryIterator<P> implements Iterator<P> {
+
+        private Iterator<P> itr;
+
+        private P next;
+
+        private ProviderFactoryIterator(Iterator<P> itr) {
+            this.itr = itr;
+            setNext();
+        }
+
+        @Override
+        public boolean hasNext() {
+            return next != null;
+        }
+
+        @Override
+        public P next() {
+            P n = next;
+            setNext();
+            return n;
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+
+        private void setNext() {
+            next = null;
+            while (itr.hasNext()) {
+                if (itr.hasNext()) {
+                    P n = itr.next();
+                    if (!System.getProperties().containsKey(n.getClass().getName() + ".disabled")) {
+                        next = n;
+                        return;
+                    }
+                }
+            }
+        }
+
+    }
+
+}

server/pom.xml 20(+20 -0)

diff --git a/server/pom.xml b/server/pom.xml
index 3a23448..3e76cac 100755
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -50,6 +50,21 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-audit-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-audit-jpa</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-audit-jboss-logging</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-social-core</artifactId>
             <version>${project.version}</version>
         </dependency>
@@ -183,6 +198,11 @@
             <version>${project.version}</version>
         </dependency>
         <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-audit-mongo</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
             <groupId>org.mongodb</groupId>
             <artifactId>mongo-java-driver</artifactId>
         </dependency>
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
index 8113188..5a582e9 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
@@ -9,6 +9,7 @@ import org.junit.Assert;
 import org.junit.rules.TestRule;
 import org.junit.runners.model.Statement;
 import org.keycloak.audit.AuditListener;
+import org.keycloak.audit.AuditListenerFactory;
 import org.keycloak.audit.Details;
 import org.keycloak.audit.Event;
 import org.keycloak.models.ClientModel;
@@ -31,7 +32,7 @@ import java.util.concurrent.TimeUnit;
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class AssertEvents implements TestRule, AuditListener{
+public class AssertEvents implements TestRule, AuditListenerFactory {
 
     private static final Logger log = Logger.getLogger(AssertEvents.class);
 
@@ -58,11 +59,6 @@ public class AssertEvents implements TestRule, AuditListener{
     }
 
     @Override
-    public void onEvent(Event event) {
-        events.add(event);
-    }
-
-    @Override
     public Statement apply(final Statement base, org.junit.runner.Description description) {
         return new Statement() {
             @Override
@@ -165,6 +161,28 @@ public class AssertEvents implements TestRule, AuditListener{
         return new ExpectedEvent().realm(DEFAULT_REALM).client(DEFAULT_CLIENT_ID).user(keycloak.getUser(DEFAULT_REALM, DEFAULT_USERNAME).getId()).ipAddress(DEFAULT_IP_ADDRESS).event(event);
     }
 
+    @Override
+    public AuditListener create() {
+        return new AuditListener() {
+            @Override
+            public void onEvent(Event event) {
+                events.add(event);
+            }
+
+            @Override
+            public void close() {
+            }
+        };
+    }
+
+    @Override
+    public void init() {
+    }
+
+    @Override
+    public void close() {
+    }
+
     public static class ExpectedEvent {
         private Event expected = new Event();
         private Matcher<String> userId;