diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/audit/AuditEvent.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/audit/AuditEvent.java index 34f9d619df8e..d1b9089c1dc8 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/audit/AuditEvent.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/audit/AuditEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -80,6 +80,7 @@ public AuditEvent(String principal, String type, String... data) { public AuditEvent(Date timestamp, String principal, String type, Map data) { Assert.notNull(timestamp, "Timestamp must not be null"); + Assert.notNull(principal, "Principal must not be null"); Assert.notNull(type, "Type must not be null"); this.timestamp = timestamp; this.principal = principal; diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/audit/AuditEventRepository.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/audit/AuditEventRepository.java index 0f966ddc6a0f..c6c9c3951f55 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/audit/AuditEventRepository.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/audit/AuditEventRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,9 +23,18 @@ * Repository for {@link AuditEvent}s. * * @author Dave Syer + * @author Vedran Pavic */ public interface AuditEventRepository { + /** + * Find audit events since the time provided. + * @param after timestamp of earliest result required + * @return audit events + * @since 1.4.0 + */ + List find(Date after); + /** * Find audit events relating to the specified principal since the time provided. * @param principal the principal name to search for @@ -34,6 +43,17 @@ public interface AuditEventRepository { */ List find(String principal, Date after); + /** + * Find audit events of specified type relating to the specified principal since the + * time provided. + * @param principal the principal name to search for + * @param type the event type to search for + * @param after timestamp of earliest result required + * @return audit events of specified type relating to the principal + * @since 1.4.0 + */ + List find(String principal, String type, Date after); + /** * Log an event. * @param event the audit event to log diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/audit/InMemoryAuditEventRepository.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/audit/InMemoryAuditEventRepository.java index 087a51f14143..2fcefcf233f3 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/audit/InMemoryAuditEventRepository.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/audit/InMemoryAuditEventRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,11 +20,14 @@ import java.util.LinkedList; import java.util.List; +import org.springframework.util.Assert; + /** * In-memory {@link AuditEventRepository} implementation. * * @author Dave Syer * @author Phillip Webb + * @author Vedran Pavic */ public class InMemoryAuditEventRepository implements AuditEventRepository { @@ -33,7 +36,7 @@ public class InMemoryAuditEventRepository implements AuditEventRepository { /** * Circular buffer of the event with tail pointing to the last element. */ - private AuditEvent[] events; + private final AuditEvent[] events; private volatile int tail = -1; @@ -45,39 +48,84 @@ public InMemoryAuditEventRepository(int capacity) { this.events = new AuditEvent[capacity]; } - /** - * Set the capacity of this event repository. - * @param capacity the capacity - */ - public synchronized void setCapacity(int capacity) { - this.events = new AuditEvent[capacity]; + @Override + public List find(Date after) { + LinkedList events = new LinkedList(); + synchronized (this.events) { + for (int i = 0; i < this.events.length; i++) { + AuditEvent event = resolveTailEvent(i); + if (event == null) { + break; + } + if (isMatch(event, after)) { + events.addFirst(event); + } + } + } + return events; } @Override - public synchronized List find(String principal, Date after) { + public List find(String principal, Date after) { + Assert.notNull(principal, "Principal must not be null"); LinkedList events = new LinkedList(); - for (int i = 0; i < this.events.length; i++) { - int index = ((this.tail + this.events.length - i) % this.events.length); - AuditEvent event = this.events[index]; - if (event == null) { - break; - } - if (isMatch(event, principal, after)) { - events.addFirst(event); + synchronized (this.events) { + for (int i = 0; i < this.events.length; i++) { + AuditEvent event = resolveTailEvent(i); + if (event == null) { + break; + } + if (isMatch(event, principal, after)) { + events.addFirst(event); + } } } return events; } - private boolean isMatch(AuditEvent auditEvent, String principal, Date after) { - return (principal == null || auditEvent.getPrincipal().equals(principal)) - && (after == null || auditEvent.getTimestamp().compareTo(after) >= 0); + @Override + public List find(String principal, String type, Date after) { + Assert.notNull(principal, "Principal must not be null"); + Assert.notNull(type, "Type must not be null"); + LinkedList events = new LinkedList(); + synchronized (this.events) { + for (int i = 0; i < this.events.length; i++) { + AuditEvent event = resolveTailEvent(i); + if (event == null) { + break; + } + if (isMatch(event, principal, type, after)) { + events.addFirst(event); + } + } + } + return events; } @Override - public synchronized void add(AuditEvent event) { - this.tail = (this.tail + 1) % this.events.length; - this.events[this.tail] = event; + public void add(AuditEvent event) { + Assert.notNull(event, "AuditEvent must not be null"); + synchronized (this.events) { + this.tail = (this.tail + 1) % this.events.length; + this.events[this.tail] = event; + } + } + + private AuditEvent resolveTailEvent(int offset) { + int index = ((this.tail + this.events.length - offset) % this.events.length); + return this.events[index]; + } + + private boolean isMatch(AuditEvent event, Date after) { + return (after == null || event.getTimestamp().compareTo(after) >= 0); + } + + private boolean isMatch(AuditEvent event, String principal, Date after) { + return (event.getPrincipal().equals(principal) && isMatch(event, after)); + } + + private boolean isMatch(AuditEvent event, String principal, String type, Date after) { + return (event.getType().equals(type) && isMatch(event, principal, after)); } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/audit/AuditEventTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/audit/AuditEventTests.java index d0f0b9c8141f..4313f5f3042c 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/audit/AuditEventTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/audit/AuditEventTests.java @@ -18,7 +18,9 @@ import java.util.Collections; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import static org.assertj.core.api.Assertions.assertThat; @@ -26,11 +28,15 @@ * Tests for {@link AuditEvent}. * * @author Dave Syer + * @author Vedran Pavic */ public class AuditEventTests { + @Rule + public ExpectedException thrown = ExpectedException.none(); + @Test - public void testNowEvent() throws Exception { + public void nowEvent() throws Exception { AuditEvent event = new AuditEvent("phil", "UNKNOWN", Collections.singletonMap("a", (Object) "b")); assertThat(event.getData().get("a")).isEqualTo("b"); @@ -40,10 +46,32 @@ public void testNowEvent() throws Exception { } @Test - public void testConvertStringsToData() throws Exception { + public void convertStringsToData() throws Exception { AuditEvent event = new AuditEvent("phil", "UNKNOWN", "a=b", "c=d"); assertThat(event.getData().get("a")).isEqualTo("b"); assertThat(event.getData().get("c")).isEqualTo("d"); } + @Test + public void nullTimestamp() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Timestamp must not be null"); + new AuditEvent(null, "phil", "UNKNOWN", + Collections.singletonMap("a", (Object) "b")); + } + + @Test + public void nullPrincipal() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Principal must not be null"); + new AuditEvent(null, "UNKNOWN", Collections.singletonMap("a", (Object) "b")); + } + + @Test + public void nullType() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Type must not be null"); + new AuditEvent("phil", null, Collections.singletonMap("a", (Object) "b")); + } + } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/audit/InMemoryAuditEventRepositoryTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/audit/InMemoryAuditEventRepositoryTests.java index 90078e94c31a..d010b31f94dd 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/audit/InMemoryAuditEventRepositoryTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/audit/InMemoryAuditEventRepositoryTests.java @@ -22,7 +22,9 @@ import java.util.List; import java.util.Map; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import static org.assertj.core.api.Assertions.assertThat; @@ -31,9 +33,13 @@ * * @author Dave Syer * @author Phillip Webb + * @author Vedran Pavic */ public class InMemoryAuditEventRepositoryTests { + @Rule + public ExpectedException thrown = ExpectedException.none(); + @Test public void lessThanCapacity() throws Exception { InMemoryAuditEventRepository repository = new InMemoryAuditEventRepository(); @@ -43,7 +49,6 @@ public void lessThanCapacity() throws Exception { assertThat(events.size()).isEqualTo(2); assertThat(events.get(0).getType()).isEqualTo("a"); assertThat(events.get(1).getType()).isEqualTo("b"); - } @Test @@ -58,6 +63,14 @@ public void capacity() throws Exception { assertThat(events.get(1).getType()).isEqualTo("c"); } + @Test + public void addNullAuditEvent() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("AuditEvent must not be null"); + InMemoryAuditEventRepository repository = new InMemoryAuditEventRepository(); + repository.add(null); + } + @Test public void findByPrincipal() throws Exception { InMemoryAuditEventRepository repository = new InMemoryAuditEventRepository(); @@ -71,6 +84,19 @@ public void findByPrincipal() throws Exception { assertThat(events.get(1).getType()).isEqualTo("c"); } + @Test + public void findByPrincipalAndType() throws Exception { + InMemoryAuditEventRepository repository = new InMemoryAuditEventRepository(); + repository.add(new AuditEvent("dave", "a")); + repository.add(new AuditEvent("phil", "b")); + repository.add(new AuditEvent("dave", "c")); + repository.add(new AuditEvent("phil", "d")); + List events = repository.find("dave", "a", null); + assertThat(events.size()).isEqualTo(1); + assertThat(events.get(0).getPrincipal()).isEqualTo("dave"); + assertThat(events.get(0).getType()).isEqualTo("a"); + } + @Test public void findByDate() throws Exception { Calendar calendar = Calendar.getInstance(); @@ -87,7 +113,7 @@ public void findByDate() throws Exception { calendar.add(Calendar.DAY_OF_YEAR, 1); repository.add(new AuditEvent(calendar.getTime(), "phil", "d", data)); calendar.add(Calendar.DAY_OF_YEAR, 1); - List events = repository.find(null, after); + List events = repository.find(after); assertThat(events.size()).isEqualTo(2); assertThat(events.get(0).getType()).isEqualTo("c"); assertThat(events.get(1).getType()).isEqualTo("d");