Skip to content

Commit

Permalink
Added annotations to REST API endpoints [#474]
Browse files Browse the repository at this point in the history
  • Loading branch information
mcpierce committed Aug 22, 2020
1 parent 02acdea commit 3aecd73
Show file tree
Hide file tree
Showing 11 changed files with 70 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,22 @@ public class RestAuditLogEntry {
@Setter
private String method;

@Column(name = "content", nullable = true, updatable = false)
@Column(name = "request_content", nullable = true, updatable = false)
@Lob
@Getter
@Setter
private String content;
private String requestContent;

@Column(name = "response_content", nullable = true, updatable = false)
@Lob
@Getter
@Setter
private String responseContent;

@Column(name = "email", nullable = true, updatable = false)
@Getter
@Setter
private String emai;
private String email;

@Column(name = "start_time", nullable = false, updatable = false)
@Getter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,11 @@
type="boolean">
<constraints nullable="false"/>
</column>
<column name="content"
<column name="request_content"
type="clob">
<constraints nullable="true"/>
</column>
<column name="response_content"
type="clob">
<constraints nullable="true"/>
</column>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.comixedproject.auditlog;

/**
* <code>AuditableEndpoint</code> is used to indicate that a method is to be audited whenever it's
* invoked. The method <b>must</b> return a {@link ApiResponse} object.
*
* @author Darryl L. Pierce
*/
public @interface AuditableEndpoint {}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses>
*/

package org.comixedproject.aspect;
package org.comixedproject.auditlog;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Date;
Expand Down Expand Up @@ -45,14 +46,15 @@
@Log4j2
public class AuditableEndpointAspect {
@Autowired private RestAuditLogService restAuditLogService;
@Autowired private ObjectMapper objectMapper;

/**
* Wraps REST API calls and records the results.
*
* @param joinPoint the join point.
* @return the response object
*/
@Around("@annotation(org.comixedproject.aspect.AuditableEndpoint)")
@Around("@annotation(org.comixedproject.auditlog.AuditableEndpoint)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Throwable error = null;
Object response = null;
Expand All @@ -71,15 +73,18 @@ public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

if (request.getUserPrincipal() != null) {
entry.setEmai(request.getUserPrincipal().getName());
entry.setEmail(request.getUserPrincipal().getName());
} else {
entry.setException("anonymous");
}
entry.setRemoteIp(request.getRemoteAddr());
entry.setUrl(request.getRequestURI());
entry.setMethod(request.getMethod());
entry.setContent(
entry.setRequestContent(
new String(((ContentCachingRequestWrapper) request).getContentAsByteArray()));
if (apiResponse.getResult() != null) {
entry.setResponseContent(this.objectMapper.writeValueAsString(apiResponse.getResult()));
}
entry.setStartTime(started);
entry.setEndTime(ended);
entry.setSuccessful(apiResponse.isSuccess());
Expand All @@ -88,7 +93,8 @@ public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
log.debug("Storing stacktrace");
final StringWriter stringWriter = new StringWriter();
final PrintWriter printWriter = new PrintWriter(stringWriter);
entry.setException(printWriter.toString());
apiResponse.getThrowable().printStackTrace(printWriter);
entry.setException(stringWriter.toString());
}

this.restAuditLogService.save(entry);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ public class FileController {

private int requestId = 0;

/**
* Retrieves all comic files under the specified directory.
*
* @param request the request body
* @return the list of comic files
* @throws IOException if an error occurs
* @throws JSONException if an error occurs
*/
@PostMapping(
value = "/contents",
produces = MediaType.APPLICATION_JSON_VALUE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.Date;
import java.util.List;
import lombok.extern.log4j.Log4j2;
import org.comixedproject.auditlog.AuditableEndpoint;
import org.comixedproject.controller.ComiXedControllerException;
import org.comixedproject.model.tasks.TaskAuditLogEntry;
import org.comixedproject.net.ApiResponse;
Expand Down Expand Up @@ -56,26 +57,29 @@ public class TaskController {
@GetMapping(value = "/entries/{cutoff}", produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasRole('ADMIN')")
@JsonView(View.TaskAuditLogEntryList.class)
@AuditableEndpoint
public ApiResponse<GetTaskAuditLogResponse> getAllAfterDate(
@PathVariable("cutoff") final Long timestamp) throws ComiXedControllerException {
ApiResponse<GetTaskAuditLogResponse> result = new ApiResponse<>();
ApiResponse<GetTaskAuditLogResponse> response = new ApiResponse<>();

final Date cutoff = new Date(timestamp);
log.debug("Getting all task audit log entries after: {}", cutoff);

try {
final List<TaskAuditLogEntry> entries = this.taskService.getAuditLogEntriesAfter(cutoff);
result.setResult(new GetTaskAuditLogResponse());
result.getResult().setEntries(entries);
result.getResult().setLatest(entries.get(entries.size() - 1).getStartTime());
result.setSuccess(true);
response.setResult(new GetTaskAuditLogResponse());
response.getResult().setEntries(entries);
if (!entries.isEmpty())
response.getResult().setLatest(entries.get(entries.size() - 1).getStartTime());
response.setSuccess(true);
} catch (ComiXedServiceException error) {
log.error("Failed to load task audit log entries", error);
result.setSuccess(false);
result.setError(error.getMessage());
response.setSuccess(false);
response.setError(error.getMessage());
response.setThrowable(error);
}

return result;
return response;
}

/**
Expand All @@ -86,6 +90,7 @@ public ApiResponse<GetTaskAuditLogResponse> getAllAfterDate(
@DeleteMapping(value = "/entries", produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasRole('ADMIN')")
@JsonView(View.ApiResponse.class)
@AuditableEndpoint
public ApiResponse<Void> clearTaskAuditLog() {
log.debug("Clearing task audit log");
final ApiResponse<Void> response = new ApiResponse<>();
Expand All @@ -97,6 +102,7 @@ public ApiResponse<Void> clearTaskAuditLog() {
log.error("Failed to clear audit log", error);
response.setSuccess(false);
response.setError(error.getMessage());
response.setThrowable(error);
}

return response;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package org.comixedproject.net;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonView;
import lombok.Getter;
Expand All @@ -43,6 +44,8 @@ public class ApiResponse<T> {
@JsonView(View.ApiResponse.class)
private String error;

@Getter @Setter @JsonIgnore private Throwable throwable;

@Getter
@Setter
@JsonProperty("result")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses>
*/

package org.comixedproject.aspect;
package org.comixedproject.auditlog;

import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertSame;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.comixedproject.model.auditlog.RestAuditLogEntry;
import org.comixedproject.net.ApiResponse;
Expand All @@ -30,23 +31,27 @@
import org.junit.runner.RunWith;
import org.mockito.*;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.util.ContentCachingRequestWrapper;

@RunWith(MockitoJUnitRunner.class)
public class AuditableEndpointAspectTest {
private static final byte[] TEST_RESPONSE_OBJECT = "JSON content".getBytes();

@InjectMocks private AuditableEndpointAspect auditableEndpointAspect;
@Mock private RestAuditLogService restAuditLogService;
@Mock private ProceedingJoinPoint proceedingJoinPoint;
@Mock private ApiResponse<?> apiResponse;
@Captor private ArgumentCaptor<RestAuditLogEntry> restAuditLogEntryArgumentCaptor;
@Mock private RestAuditLogEntry savedEntry;
@Mock private ObjectMapper objectMapper;
@Mock private ContentCachingRequestWrapper requestWrapper;

@Before
public void setUp() {
MockHttpServletRequest request = new MockHttpServletRequest();
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(requestWrapper));
Mockito.when(requestWrapper.getContentAsByteArray()).thenReturn(TEST_RESPONSE_OBJECT);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
* @author Darryl L. Pierce
*/
public class ComiXedServiceException extends Exception {
public ComiXedServiceException(final String message, final InterruptedException cause) {
public ComiXedServiceException(final String message, final Exception cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public List<TaskAuditLogEntry> getAuditLogEntriesAfter(final Date cutoff)
List<TaskAuditLogEntry> result = null;
boolean done = false;
long started = System.currentTimeMillis();

while (!done) {
result = this.taskAuditLogRepository.findAllByStartTimeGreaterThanOrderByStartTime(cutoff);

Expand Down

0 comments on commit 3aecd73

Please sign in to comment.