From a310a79909bb102f7e25e1d20f17599fba65b72b Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Thu, 2 May 2013 09:22:59 +0100 Subject: [PATCH] [bs-15] Add audit abstraction and sensible opinionated defaults * Added AuditEvent and AuditEventRepository * Also AuditApplicationEvent and AuditListener for handling AUditEvents as Spring ApplicationEvents [Fixes #48155753] --- .../bootstrap/service/audit/AuditEvent.java | 93 +++++++++++++++++++ .../service/audit/AuditEventRepository.java | 43 +++++++++ .../audit/InMemoryAuditEventRepository.java | 67 +++++++++++++ .../audit/listener/AuditApplicationEvent.java | 43 +++++++++ .../service/audit/listener/AuditListener.java | 38 ++++++++ .../service/audit/AuditEventTests.java | 48 ++++++++++ .../InMemoryAuditEventRepositoryTests.java | 43 +++++++++ 7 files changed, 375 insertions(+) create mode 100644 spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/AuditEvent.java create mode 100644 spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/AuditEventRepository.java create mode 100644 spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/InMemoryAuditEventRepository.java create mode 100644 spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/listener/AuditApplicationEvent.java create mode 100644 spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/listener/AuditListener.java create mode 100644 spring-bootstrap-service/src/test/java/org/springframework/bootstrap/service/audit/AuditEventTests.java create mode 100644 spring-bootstrap-service/src/test/java/org/springframework/bootstrap/service/audit/InMemoryAuditEventRepositoryTests.java diff --git a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/AuditEvent.java b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/AuditEvent.java new file mode 100644 index 00000000000..c76b689b8a3 --- /dev/null +++ b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/AuditEvent.java @@ -0,0 +1,93 @@ +/* + * Copyright 2012-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.bootstrap.service.audit; + +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * A value object representing an audit event: at a particular time, a particular user or + * agent carried out an action of a particular type. This object records the details of + * such an event. + * + * @author Dave Syer + * + */ +public class AuditEvent { + + final private Date timestamp; + final private String principal; + final private String type; + final private Map data; + + /** + * Create a new audit event for the current time from data provided as name-value + * pairs + */ + public AuditEvent(String principal, String type, String... data) { + this(new Date(), principal, type, convert(data)); + } + + /** + * Create a new audit event for the current time + */ + public AuditEvent(String principal, String type, Map data) { + this(new Date(), principal, type, data); + } + + /** + * Create a new audit event. + */ + public AuditEvent(Date timestamp, String principal, String type, + Map data) { + this.timestamp = timestamp; + this.principal = principal; + this.type = type; + this.data = Collections.unmodifiableMap(data); + } + + public Date getTimestamp() { + return this.timestamp; + } + + public String getPrincipal() { + return this.principal; + } + + public String getType() { + return this.type; + } + + public Map getData() { + return this.data; + } + + private static Map convert(String[] data) { + Map result = new HashMap(); + for (String entry : data) { + if (entry.contains("=")) { + int index = entry.indexOf("="); + result.put(entry.substring(0, index), entry.substring(index + 1)); + } else { + result.put(entry, null); + } + } + return result; + } + +} diff --git a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/AuditEventRepository.java b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/AuditEventRepository.java new file mode 100644 index 00000000000..ab66ce5e512 --- /dev/null +++ b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/AuditEventRepository.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.bootstrap.service.audit; + +import java.util.Date; +import java.util.List; + +/** + * @author Dave Syer + * + */ +public interface AuditEventRepository { + + /** + * Find audit events relating to the specified principal since the time provided. + * + * @param principal the principal name to search for + * @param after timestamp of earliest result required + * @return audit events relating to the principal + */ + List find(String principal, Date after); + + /** + * Log an event. + * + * @param event the audit event to log + */ + void add(AuditEvent event); + +} diff --git a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/InMemoryAuditEventRepository.java b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/InMemoryAuditEventRepository.java new file mode 100644 index 00000000000..45eac780f0f --- /dev/null +++ b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/InMemoryAuditEventRepository.java @@ -0,0 +1,67 @@ +/* + * Copyright 2012-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.bootstrap.service.audit; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Dave Syer + * + */ +public class InMemoryAuditEventRepository implements AuditEventRepository { + + private int capacity = 100; + + private Map> events = new HashMap>(); + + /** + * @param capacity the capacity to set + */ + public void setCapacity(int capacity) { + this.capacity = capacity; + } + + @Override + public List find(String principal, Date after) { + synchronized (this.events) { + return Collections.unmodifiableList(getEvents(principal)); + } + } + + private List getEvents(String principal) { + if (!this.events.containsKey(principal)) { + this.events.put(principal, new ArrayList()); + } + return this.events.get(principal); + } + + @Override + public void add(AuditEvent event) { + synchronized (this.events) { + List list = getEvents(event.getPrincipal()); + while (list.size() >= this.capacity) { + list.remove(0); + } + list.add(event); + } + } + +} diff --git a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/listener/AuditApplicationEvent.java b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/listener/AuditApplicationEvent.java new file mode 100644 index 00000000000..5993632cc52 --- /dev/null +++ b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/listener/AuditApplicationEvent.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.bootstrap.service.audit.listener; + +import org.springframework.bootstrap.service.audit.AuditEvent; +import org.springframework.context.ApplicationEvent; + +/** + * @author Dave Syer + * + */ +public class AuditApplicationEvent extends ApplicationEvent { + + private AuditEvent auditEvent; + + /** + * @param auditEvent the source of this event + */ + public AuditApplicationEvent(AuditEvent auditEvent) { + super(auditEvent); + this.auditEvent = auditEvent; + } + + /** + * @return the audit event + */ + public AuditEvent getAuditEvent() { + return this.auditEvent; + } +} diff --git a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/listener/AuditListener.java b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/listener/AuditListener.java new file mode 100644 index 00000000000..6c1bd1d01fe --- /dev/null +++ b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/listener/AuditListener.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.bootstrap.service.audit.listener; + +import org.springframework.bootstrap.service.audit.AuditEventRepository; +import org.springframework.context.ApplicationListener; + +/** + * @author Dave Syer + * + */ +public class AuditListener implements ApplicationListener { + + private final AuditEventRepository auditEventRepository; + + public AuditListener(AuditEventRepository auditEventRepository) { + this.auditEventRepository = auditEventRepository; + } + + @Override + public void onApplicationEvent(AuditApplicationEvent event) { + this.auditEventRepository.add(event.getAuditEvent()); + } + +} \ No newline at end of file diff --git a/spring-bootstrap-service/src/test/java/org/springframework/bootstrap/service/audit/AuditEventTests.java b/spring-bootstrap-service/src/test/java/org/springframework/bootstrap/service/audit/AuditEventTests.java new file mode 100644 index 00000000000..26465ea4a8b --- /dev/null +++ b/spring-bootstrap-service/src/test/java/org/springframework/bootstrap/service/audit/AuditEventTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.bootstrap.service.audit; + +import java.util.Collections; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * @author Dave Syer + * + */ +public class AuditEventTests { + + @Test + public void testNowEvent() throws Exception { + AuditEvent event = new AuditEvent("phil", "UNKNOWN", Collections.singletonMap( + "a", (Object) "b")); + assertEquals("b", event.getData().get("a")); + assertEquals("UNKNOWN", event.getType()); + assertEquals("phil", event.getPrincipal()); + assertNotNull(event.getTimestamp()); + } + + @Test + public void testConvertStringsToData() throws Exception { + AuditEvent event = new AuditEvent("phil", "UNKNOWN", "a=b", "c=d"); + assertEquals("b", event.getData().get("a")); + assertEquals("d", event.getData().get("c")); + } + +} diff --git a/spring-bootstrap-service/src/test/java/org/springframework/bootstrap/service/audit/InMemoryAuditEventRepositoryTests.java b/spring-bootstrap-service/src/test/java/org/springframework/bootstrap/service/audit/InMemoryAuditEventRepositoryTests.java new file mode 100644 index 00000000000..595f21328d5 --- /dev/null +++ b/spring-bootstrap-service/src/test/java/org/springframework/bootstrap/service/audit/InMemoryAuditEventRepositoryTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.bootstrap.service.audit; + +import java.util.Date; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * @author Dave Syer + * + */ +public class InMemoryAuditEventRepositoryTests { + + private InMemoryAuditEventRepository repository = new InMemoryAuditEventRepository(); + + @Test + public void testAddToCapacity() throws Exception { + this.repository.setCapacity(2); + this.repository.add(new AuditEvent("phil", "UNKNOWN")); + this.repository.add(new AuditEvent("phil", "UNKNOWN")); + this.repository.add(new AuditEvent("dave", "UNKNOWN")); + this.repository.add(new AuditEvent("dave", "UNKNOWN")); + this.repository.add(new AuditEvent("phil", "UNKNOWN")); + assertEquals(2, this.repository.find("phil", new Date(0L)).size()); + } + +}