[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]
This commit is contained in:
Dave Syer 2013-05-02 09:22:59 +01:00
parent cee78386ee
commit a310a79909
7 changed files with 375 additions and 0 deletions

View File

@ -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<String, Object> 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<String, Object> data) {
this(new Date(), principal, type, data);
}
/**
* Create a new audit event.
*/
public AuditEvent(Date timestamp, String principal, String type,
Map<String, Object> 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<String, Object> getData() {
return this.data;
}
private static Map<String, Object> convert(String[] data) {
Map<String, Object> result = new HashMap<String, Object>();
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;
}
}

View File

@ -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<AuditEvent> find(String principal, Date after);
/**
* Log an event.
*
* @param event the audit event to log
*/
void add(AuditEvent event);
}

View File

@ -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<String, List<AuditEvent>> events = new HashMap<String, List<AuditEvent>>();
/**
* @param capacity the capacity to set
*/
public void setCapacity(int capacity) {
this.capacity = capacity;
}
@Override
public List<AuditEvent> find(String principal, Date after) {
synchronized (this.events) {
return Collections.unmodifiableList(getEvents(principal));
}
}
private List<AuditEvent> getEvents(String principal) {
if (!this.events.containsKey(principal)) {
this.events.put(principal, new ArrayList<AuditEvent>());
}
return this.events.get(principal);
}
@Override
public void add(AuditEvent event) {
synchronized (this.events) {
List<AuditEvent> list = getEvents(event.getPrincipal());
while (list.size() >= this.capacity) {
list.remove(0);
}
list.add(event);
}
}
}

View File

@ -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;
}
}

View File

@ -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<AuditApplicationEvent> {
private final AuditEventRepository auditEventRepository;
public AuditListener(AuditEventRepository auditEventRepository) {
this.auditEventRepository = auditEventRepository;
}
@Override
public void onApplicationEvent(AuditApplicationEvent event) {
this.auditEventRepository.add(event.getAuditEvent());
}
}

View File

@ -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"));
}
}

View File

@ -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());
}
}