diff --git a/pom.xml b/pom.xml index f44b0e08a..607ae5618 100644 --- a/pom.xml +++ b/pom.xml @@ -296,6 +296,7 @@ false /tmp/atlas/audit/audit.log + /tmp/atlas/audit/audit-%d{yyyy-MM-dd}-%i.log /tmp/atlas/audit/audit-extra.log diff --git a/src/main/java/org/ohdsi/webapi/statistic/controller/StatisticController.java b/src/main/java/org/ohdsi/webapi/statistic/controller/StatisticController.java new file mode 100644 index 000000000..b2fe8552e --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/statistic/controller/StatisticController.java @@ -0,0 +1,255 @@ +package org.ohdsi.webapi.statistic.controller; + +import com.opencsv.CSVWriter; + +import org.ohdsi.webapi.statistic.dto.AccessTrendDto; +import org.ohdsi.webapi.statistic.dto.AccessTrendsDto; +import org.ohdsi.webapi.statistic.dto.EndpointDto; +import org.ohdsi.webapi.statistic.dto.SourceExecutionDto; +import org.ohdsi.webapi.statistic.dto.SourceExecutionsDto; +import org.ohdsi.webapi.statistic.service.StatisticService; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Controller; + +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import java.io.ByteArrayOutputStream; +import java.io.StringWriter; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Controller +@Path("/statistic/") +@ConditionalOnProperty(value = "audit.trail.enabled", havingValue = "true") +public class StatisticController { + private StatisticService service; + + public enum ResponseFormat { + CSV, JSON + } + + private static final List EXECUTION_STATISTICS_CSV_RESULT_HEADER = new ArrayList() {{ + add(new String[]{"Date", "Source", "Execution Type"}); + }}; + + private static final List ACCESS_TRENDS_CSV_RESULT_HEADER = new ArrayList() {{ + add(new String[]{"Date", "Endpoint", "UserID"}); + }}; + + public StatisticController(StatisticService service) { + this.service = service; + } + + /** + * Returns execution statistics + * @param executionStatisticsRequest - filter settings for statistics + */ + @POST + @Path("/executions") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Response executionStatistics(ExecutionStatisticsRequest executionStatisticsRequest) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + boolean showUserInformation = executionStatisticsRequest.isShowUserInformation(); + + SourceExecutionsDto sourceExecutions = service.getSourceExecutions(LocalDate.parse(executionStatisticsRequest.getStartDate(), formatter), + LocalDate.parse(executionStatisticsRequest.getEndDate(), formatter), executionStatisticsRequest.getSourceKey(), showUserInformation); + + if (ResponseFormat.CSV.equals(executionStatisticsRequest.getResponseFormat())) { + return prepareExecutionResultResponse(sourceExecutions.getExecutions(), "execution_statistics.zip", showUserInformation); + } else { + return Response.ok(sourceExecutions).build(); + } + } + + /** + * Returns access trends statistics + * @param accessTrendsStatisticsRequest - filter settings for statistics + */ + @POST + @Path("/accesstrends") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Response accessStatistics(AccessTrendsStatisticsRequest accessTrendsStatisticsRequest) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + boolean showUserInformation = accessTrendsStatisticsRequest.isShowUserInformation(); + + AccessTrendsDto trends = service.getAccessTrends(LocalDate.parse(accessTrendsStatisticsRequest.getStartDate(), formatter), + LocalDate.parse(accessTrendsStatisticsRequest.getEndDate(), formatter), accessTrendsStatisticsRequest.getEndpoints(), showUserInformation); + + if (ResponseFormat.CSV.equals(accessTrendsStatisticsRequest.getResponseFormat())) { + return prepareAccessTrendsResponse(trends.getTrends(), "execution_trends.zip", showUserInformation); + } else { + return Response.ok(trends).build(); + } + } + + private Response prepareExecutionResultResponse(List executions, String filename, boolean showUserInformation) { + updateExecutionStatisticsHeader(showUserInformation); + List data = executions.stream() + .map(execution -> showUserInformation + ? new String[]{execution.getExecutionDate(), execution.getSourceName(), execution.getExecutionName(), execution.getUserID()} + : new String[]{execution.getExecutionDate(), execution.getSourceName(), execution.getExecutionName()} + ) + .collect(Collectors.toList()); + return prepareResponse(data, filename, EXECUTION_STATISTICS_CSV_RESULT_HEADER); + } + + private Response prepareAccessTrendsResponse(List trends, String filename, boolean showUserInformation) { + updateAccessTrendsHeader(showUserInformation); + List data = trends.stream() + .map(trend -> showUserInformation + ? new String[]{trend.getExecutionDate().toString(), trend.getEndpointName(), trend.getUserID()} + : new String[]{trend.getExecutionDate().toString(), trend.getEndpointName()} + ) + .collect(Collectors.toList()); + return prepareResponse(data, filename, ACCESS_TRENDS_CSV_RESULT_HEADER); + } + + private Response prepareResponse(List data, String filename, List header) { + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + StringWriter sw = new StringWriter(); + CSVWriter csvWriter = new CSVWriter(sw, ',', CSVWriter.DEFAULT_QUOTE_CHARACTER, CSVWriter.DEFAULT_ESCAPE_CHARACTER); + csvWriter.writeAll(header); + csvWriter.writeAll(data); + csvWriter.flush(); + baos.write(sw.getBuffer().toString().getBytes()); + + return Response + .ok(baos) + .type(MediaType.APPLICATION_OCTET_STREAM) + .header("Content-Disposition", String.format("attachment; filename=\"%s\"", filename)) + .build(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + private void updateExecutionStatisticsHeader(boolean showUserInformation) { + EXECUTION_STATISTICS_CSV_RESULT_HEADER.clear(); + if (showUserInformation) { + EXECUTION_STATISTICS_CSV_RESULT_HEADER.add(new String[]{"Date", "Source", "Execution Type", "User ID"}); + } else { + EXECUTION_STATISTICS_CSV_RESULT_HEADER.add(new String[]{"Date", "Source", "Execution Type"}); + } + } + + private void updateAccessTrendsHeader(boolean showUserInformation) { + ACCESS_TRENDS_CSV_RESULT_HEADER.clear(); + if (showUserInformation) { + ACCESS_TRENDS_CSV_RESULT_HEADER.add(new String[]{"Date", "Endpoint", "UserID"}); + } else { + ACCESS_TRENDS_CSV_RESULT_HEADER.add(new String[]{"Date", "Endpoint"}); + } + } + + public static final class ExecutionStatisticsRequest { + // Format - yyyy-MM-dd + String startDate; + // Format - yyyy-MM-dd + String endDate; + String sourceKey; + ResponseFormat responseFormat; + boolean showUserInformation; + + public String getStartDate() { + return startDate; + } + + public void setStartDate(String startDate) { + this.startDate = startDate; + } + + public String getEndDate() { + return endDate; + } + + public void setEndDate(String endDate) { + this.endDate = endDate; + } + + public String getSourceKey() { + return sourceKey; + } + + public void setSourceKey(String sourceKey) { + this.sourceKey = sourceKey; + } + + public ResponseFormat getResponseFormat() { + return responseFormat; + } + + public void setResponseFormat(ResponseFormat responseFormat) { + this.responseFormat = responseFormat; + } + + public boolean isShowUserInformation() { + return showUserInformation; + } + + public void setShowUserInformation(boolean showUserInformation) { + this.showUserInformation = showUserInformation; + } + } + + public static final class AccessTrendsStatisticsRequest { + // Format - yyyy-MM-dd + String startDate; + // Format - yyyy-MM-dd + String endDate; + // Key - method (POST, GET) + // Value - endpoint ("{}" can be used as a placeholder, will be converted to ".*" in regular expression) + List endpoints; + ResponseFormat responseFormat; + boolean showUserInformation; + + public String getStartDate() { + return startDate; + } + + public void setStartDate(String startDate) { + this.startDate = startDate; + } + + public String getEndDate() { + return endDate; + } + + public void setEndDate(String endDate) { + this.endDate = endDate; + } + + public List getEndpoints() { + return endpoints; + } + + public void setEndpoints(List endpoints) { + this.endpoints = endpoints; + } + + public ResponseFormat getResponseFormat() { + return responseFormat; + } + + public void setResponseFormat(ResponseFormat responseFormat) { + this.responseFormat = responseFormat; + } + + public boolean isShowUserInformation() { + return showUserInformation; + } + + public void setShowUserInformation(boolean showUserInformation) { + this.showUserInformation = showUserInformation; + } + } +} diff --git a/src/main/java/org/ohdsi/webapi/statistic/dto/AccessTrendDto.java b/src/main/java/org/ohdsi/webapi/statistic/dto/AccessTrendDto.java new file mode 100644 index 000000000..4a3511373 --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/statistic/dto/AccessTrendDto.java @@ -0,0 +1,37 @@ +package org.ohdsi.webapi.statistic.dto; + +public class AccessTrendDto { + private String endpointName; + private String executionDate; + private String userID; + + public AccessTrendDto(String endpointName, String executionDate, String userID) { + this.endpointName = endpointName; + this.executionDate = executionDate; + this.userID = userID; + } + + public String getEndpointName() { + return endpointName; + } + + public void setEndpointName(String endpointName) { + this.endpointName = endpointName; + } + + public String getExecutionDate() { + return executionDate; + } + + public void setExecutionDate(String executionDate) { + this.executionDate = executionDate; + } + + public String getUserID() { + return userID; + } + + public void setUserID(String userID) { + this.userID = userID; + } +} diff --git a/src/main/java/org/ohdsi/webapi/statistic/dto/AccessTrendsDto.java b/src/main/java/org/ohdsi/webapi/statistic/dto/AccessTrendsDto.java new file mode 100644 index 000000000..7c80e8c80 --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/statistic/dto/AccessTrendsDto.java @@ -0,0 +1,16 @@ +package org.ohdsi.webapi.statistic.dto; + +import java.util.ArrayList; +import java.util.List; + +public class AccessTrendsDto { + private List trends = new ArrayList<>(); + + public AccessTrendsDto(List trends) { + this.trends = trends; + } + + public List getTrends() { + return trends; + } +} diff --git a/src/main/java/org/ohdsi/webapi/statistic/dto/EndpointDto.java b/src/main/java/org/ohdsi/webapi/statistic/dto/EndpointDto.java new file mode 100644 index 000000000..5fe302953 --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/statistic/dto/EndpointDto.java @@ -0,0 +1,32 @@ +package org.ohdsi.webapi.statistic.dto; + +public class EndpointDto { + String method; + String urlPattern; + String userId; + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public String getUrlPattern() { + return urlPattern; + } + + public void setUrlPattern(String urlPattern) { + this.urlPattern = urlPattern; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } +} + diff --git a/src/main/java/org/ohdsi/webapi/statistic/dto/SourceExecutionDto.java b/src/main/java/org/ohdsi/webapi/statistic/dto/SourceExecutionDto.java new file mode 100644 index 000000000..bfda776db --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/statistic/dto/SourceExecutionDto.java @@ -0,0 +1,50 @@ +package org.ohdsi.webapi.statistic.dto; + +import java.time.Instant; +import java.time.LocalDate; + +public class SourceExecutionDto { + private String sourceName; + private String executionName; + private String executionDate; + private String userID; + + public SourceExecutionDto(String sourceName, String executionName, String executionDate, String userID) { + this.sourceName = sourceName; + this.executionName = executionName; + this.executionDate = executionDate; + this.userID = userID; + } + + public String getSourceName() { + return sourceName; + } + + public void setSourceName(String sourceName) { + this.sourceName = sourceName; + } + + public String getExecutionName() { + return executionName; + } + + public void setExecutionName(String executionName) { + this.executionName = executionName; + } + + public String getExecutionDate() { + return executionDate; + } + + public void setExecutionDate(String executionDate) { + this.executionDate = executionDate; + } + + public String getUserID() { + return userID; + } + + public void setUserID(String userID) { + this.userID = userID; + } +} diff --git a/src/main/java/org/ohdsi/webapi/statistic/dto/SourceExecutionsDto.java b/src/main/java/org/ohdsi/webapi/statistic/dto/SourceExecutionsDto.java new file mode 100644 index 000000000..e9d48b15b --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/statistic/dto/SourceExecutionsDto.java @@ -0,0 +1,16 @@ +package org.ohdsi.webapi.statistic.dto; + +import java.util.ArrayList; +import java.util.List; + +public class SourceExecutionsDto { + private List executions = new ArrayList<>(); + + public SourceExecutionsDto(List executions) { + this.executions = executions; + } + + public List getExecutions() { + return executions; + } +} diff --git a/src/main/java/org/ohdsi/webapi/statistic/service/StatisticService.java b/src/main/java/org/ohdsi/webapi/statistic/service/StatisticService.java new file mode 100644 index 000000000..7bb00cfef --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/statistic/service/StatisticService.java @@ -0,0 +1,230 @@ +package org.ohdsi.webapi.statistic.service; + +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.ohdsi.webapi.statistic.dto.AccessTrendDto; +import org.ohdsi.webapi.statistic.dto.AccessTrendsDto; +import org.ohdsi.webapi.statistic.dto.EndpointDto; +import org.ohdsi.webapi.statistic.dto.SourceExecutionDto; +import org.ohdsi.webapi.statistic.dto.SourceExecutionsDto; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Service +@ConditionalOnProperty(value = "audit.trail.enabled", havingValue = "true") +public class StatisticService { + protected final Logger LOG = LoggerFactory.getLogger(getClass()); + + @Value("${audit.trail.log.file}") + // TODO remove value + private String absoluteLogFileName = "/tmp/atlas/audit/audit.log"; + + private String logFileName; + + @Value("${audit.trail.log.file.pattern}") + // TODO remove value + private String absoluteLogFileNamePattern = "/tmp/atlas/audit/audit-%d{yyyy-MM-dd}-%i.log"; + + private String logFileNamePattern; + + private SimpleDateFormat logFileDateFormat; + + private int logFileDateStart; + + private int logFileDateEnd; + + // Some execution can have duplicate logs with different parameters + // Duplicate log entries can exist because sometimes ccontroller methods are called from other controller methods + // These regular expressions let us to choose only needed log entries + private static final Pattern COHORT_GENERATION_REGEXP = + Pattern.compile("^.*(\\d{4}-\\d{2}-\\d{2})T\\d{2}:\\d{2}:\\d{2}.*-\\s-\\s-\\s([\\w-]+)\\s.*GET\\s/WebAPI/cohortdefinition/\\d+/generate/(.+)\\s-\\s.*status::String,startDate::Date,endDate::Date.*$"); + + private static final Pattern CHARACTERIZATION_GENERATION_REGEXP = + Pattern.compile("^.*(\\d{4}-\\d{2}-\\d{2})T\\d{2}:\\d{2}:\\d{2}.*-\\s-\\s-\\s([\\w-]+)\\s.*POST\\s/WebAPI/cohort-characterization/\\d+/generation/(.+)\\s-\\s.*status::String,startDate::Date,endDate::Date.*$"); + + private static final Pattern PATHWAY_GENERATION_REGEXP = + Pattern.compile("^.*(\\d{4}-\\d{2}-\\d{2})T\\d{2}:\\d{2}:\\d{2}.*-\\s-\\s-\\s([\\w-]+)\\s.*POST\\s/WebAPI/pathway-analysis/\\d+/generation/(.+)\\s-\\s.*status::String,startDate::Date,endDate::Date.*$"); + + private static final Pattern IR_GENERATION_REGEXP = + Pattern.compile("^.*(\\d{4}-\\d{2}-\\d{2})T\\d{2}:\\d{2}:\\d{2}.*-\\s-\\s-\\s([\\w-]+)\\s.*GET\\s/WebAPI/ir/\\d+/execute/(.+)\\s-\\s.*status::String,startDate::Date,endDate::Date.*$"); + + private static final Pattern PLE_GENERATION_REGEXP = + Pattern.compile("^.*(\\d{4}-\\d{2}-\\d{2})T\\d{2}:\\d{2}:\\d{2}.*-\\s-\\s-\\s([\\w-]+)\\s.*POST\\s/WebAPI/estimation/\\d+/generation/(.+)\\s-\\s.*status::String,startDate::Date,endDate::Date.*$"); + + private static final Pattern PLP_GENERATION_REGEXP = + Pattern.compile("^.*(\\d{4}-\\d{2}-\\d{2})T\\d{2}:\\d{2}:\\d{2}.*-\\s-\\s-\\s([\\w-]+)\\s.*POST\\s/WebAPI/prediction/\\d+/generation/(.+)\\s-\\s.*status::String,startDate::Date,endDate::Date.*$"); + + private static final String ENDPOINT_REGEXP = + "^.*(\\d{4}-\\d{2}-\\d{2})T(\\d{2}:\\d{2}:\\d{2}).*-\\s-\\s-\\s([\\w-]+)\\s.*-\\s({METHOD_PLACEHOLDER}\\s.*{ENDPOINT_PLACEHOLDER})\\s-.*$"; + + private static final String COHORT_GENERATION_NAME = "Cohort Generation"; + + private static final String CHARACTERIZATION_GENERATION_NAME = "Characterization Generation"; + + private static final String PATHWAY_GENERATION_NAME = "Pathway Generation"; + + private static final String IR_GENERATION_NAME = "Incidence Rates Generation"; + + private static final String PLE_GENERATION_NAME = "Estimation Generation"; + + private static final String PLP_GENERATION_NAME = "Prediction Generation"; + + private static final Map patternMap = new HashMap<>(); + + static { + patternMap.put(COHORT_GENERATION_NAME, COHORT_GENERATION_REGEXP); + patternMap.put(CHARACTERIZATION_GENERATION_NAME, CHARACTERIZATION_GENERATION_REGEXP); + patternMap.put(PATHWAY_GENERATION_NAME, PATHWAY_GENERATION_REGEXP); + patternMap.put(IR_GENERATION_NAME, IR_GENERATION_REGEXP); + patternMap.put(PLE_GENERATION_NAME, PLE_GENERATION_REGEXP); + patternMap.put(PLP_GENERATION_NAME, PLP_GENERATION_REGEXP); + } + + public StatisticService() { + logFileName = new File(absoluteLogFileName).getName(); + logFileNamePattern = new File(absoluteLogFileNamePattern).getName(); + + // Pattern contains "%d{yyyy-MM-dd}". "%d" will not be contained in real log file name + int placeHolderPrefixLength = 3; + logFileDateStart = logFileNamePattern.indexOf("{") - placeHolderPrefixLength + 1; + logFileDateEnd = logFileNamePattern.indexOf("}") - placeHolderPrefixLength; + String dateString = logFileNamePattern.substring(logFileDateStart + placeHolderPrefixLength, + logFileDateEnd + placeHolderPrefixLength); + logFileDateFormat = new SimpleDateFormat(dateString); + } + + public SourceExecutionsDto getSourceExecutions(LocalDate startDate, LocalDate endDate, String sourceKey, boolean showUserInformation) { + Set paths = getLogPaths(startDate, endDate); + List executions = paths.stream() + .flatMap(path -> extractSourceExecutions(path, sourceKey, showUserInformation).stream()) + .collect(Collectors.toList()); + return new SourceExecutionsDto(executions); + } + + public AccessTrendsDto getAccessTrends(LocalDate startDate, LocalDate endDate, List endpoints, boolean showUserInformation) { + Set paths = getLogPaths(startDate, endDate); + List trends = paths.stream() + .flatMap(path -> extractAccessTrends(path, endpoints, showUserInformation).stream()) + .collect(Collectors.toList()); + return new AccessTrendsDto(trends); + } + + private List extractSourceExecutions(Path path, String sourceKey, boolean showUserInformation) { + try (Stream stream = Files.lines(path)) { + return stream + .map(str -> getMatchedExecution(str, sourceKey, showUserInformation)) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + } catch (IOException e) { + LOG.error("Error parsing log file {}. {}", path.getFileName(), e); + throw new RuntimeException(e); + } + } + + private List extractAccessTrends(Path path, List endpoints, boolean showUserInformation) { + List patterns = endpoints.stream() + .map(endpointPair -> { + String method = endpointPair.getMethod(); + + String endpoint = endpointPair.getUrlPattern().replaceAll("\\{\\}", ".*"); + String regexpStr = ENDPOINT_REGEXP.replace("{METHOD_PLACEHOLDER}", method); + regexpStr = regexpStr.replace("{ENDPOINT_PLACEHOLDER}", endpoint); + + return Pattern.compile(regexpStr); + }) + + .collect(Collectors.toList()); + try (Stream stream = Files.lines(path)) { + return stream + .map(str -> { + return patterns.stream() + .map(pattern -> pattern.matcher(str)) + .filter(Matcher::matches) + .map(matcher -> new AccessTrendDto(matcher.group(4), matcher.group(1), showUserInformation ? matcher.group(3) : null)) + .findFirst(); + }) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + } catch (IOException e) { + LOG.error("Error parsing log file {}. {}", path.getFileName(), e); + throw new RuntimeException(e); + } + } + + private Optional getMatchedExecution(String str, String sourceKey, boolean showUserInformation) { + return patternMap.entrySet().stream() + .map(entry -> new ImmutablePair<>(entry.getKey(), entry.getValue().matcher(str))) + .filter(pair -> pair.getValue().matches()) + .filter(pair -> sourceKey == null || (sourceKey != null && sourceKey.equals(pair.getValue().group(3)))) + .map(pair -> new SourceExecutionDto(pair.getValue().group(3), pair.getKey(), pair.getValue().group(1), showUserInformation ? pair.getValue().group(2) : null)) + .findFirst(); + } + + private Set getLogPaths(LocalDate startDate, LocalDate endDate) { + String folderPath = new File(absoluteLogFileName).getParentFile().getAbsolutePath(); + try (Stream stream = Files.list(Paths.get(folderPath))) { + return stream + .filter(file -> !Files.isDirectory(file)) + .filter(this::isValidLogFile) + .filter(file -> isLogInDateRange(file, startDate, endDate)) + .map(Path::toAbsolutePath) + .collect(Collectors.toSet()); + } catch (IOException e) { + LOG.error("Error getting list of log files", e); + throw new RuntimeException(e); + } + } + + private boolean isValidLogFile(Path path) { + return path.getFileName().toString().endsWith(".log"); + } + + private boolean isLogInDateRange(Path path, LocalDate startDate, LocalDate endDate) { + if (startDate == null && endDate == null) { + return true; + } + LocalDate logDate = getFileDate(path.getFileName()); + if ((startDate != null && logDate.isBefore(startDate)) + || (endDate != null && logDate.isAfter(endDate))) { + return false; + } + return true; + } + + private LocalDate getFileDate(Path path) { + String fileName = path.toString(); + if (logFileName.equals(fileName)) { + return LocalDate.now(); + } + try { + String dateStr = fileName.substring(logFileDateStart, logFileDateEnd); + return logFileDateFormat.parse(dateStr).toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + } catch (ParseException | IndexOutOfBoundsException e) { + // If we cannot check the date of a file, then assume that it is a file for the current date + return LocalDate.now(); + } + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index cd1afb201..fef844aaa 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -277,4 +277,5 @@ versioning.maxAttempt=${versioning.maxAttempt} #Audit trail audit.trail.enabled=${audit.trail.enabled} audit.trail.log.file=${audit.trail.log.file} +audit.trail.log.file.pattern=${audit.trail.log.file.pattern} audit.trail.log.extraFile=${audit.trail.log.extraFile} diff --git a/src/main/resources/db/migration/postgresql/V2.13.1.20231002112232__source_execution_satatistics.sql b/src/main/resources/db/migration/postgresql/V2.13.1.20231002112232__source_execution_satatistics.sql new file mode 100644 index 000000000..6f26e7822 --- /dev/null +++ b/src/main/resources/db/migration/postgresql/V2.13.1.20231002112232__source_execution_satatistics.sql @@ -0,0 +1,15 @@ +INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES + (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'statistic:executions:get', 'Source execution statistics permission'); + +INSERT INTO ${ohdsiSchema}.sec_permission(id, value, description) VALUES + (nextval('${ohdsiSchema}.sec_permission_id_seq'), 'statistic:accesstrends:post', 'Access trends statistics permission'); + +INSERT INTO ${ohdsiSchema}.sec_role_permission(id, role_id, permission_id) +SELECT nextval('${ohdsiSchema}.sec_role_permission_sequence'), sr.id, sp.id +FROM ${ohdsiSchema}.sec_permission SP, ${ohdsiSchema}.sec_role sr +WHERE sp.value IN ('statistic:executions:get') AND sr.name IN ('admin'); + +INSERT INTO ${ohdsiSchema}.sec_role_permission(id, role_id, permission_id) +SELECT nextval('${ohdsiSchema}.sec_role_permission_sequence'), sr.id, sp.id +FROM ${ohdsiSchema}.sec_permission SP, ${ohdsiSchema}.sec_role sr +WHERE sp.value IN ('statistic:accesstrends:post') AND sr.name IN ('admin'); \ No newline at end of file diff --git a/src/main/resources/db/migration/postgresql/V2.14.0.20231206190600__fixing_statistics_enpoint_permission_method.sql b/src/main/resources/db/migration/postgresql/V2.14.0.20231206190600__fixing_statistics_enpoint_permission_method.sql new file mode 100644 index 000000000..df5fe8b3c --- /dev/null +++ b/src/main/resources/db/migration/postgresql/V2.14.0.20231206190600__fixing_statistics_enpoint_permission_method.sql @@ -0,0 +1,2 @@ +UPDATE ${ohdsiSchema}.sec_permission SET value='statistic:executions:post' +WHERE value='statistic:executions:get'; \ No newline at end of file diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 011405298..6e5b5674e 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -2,7 +2,8 @@ ${bundle:application:audit.trail.log.extraFile} - ${bundle:application:audit.trail.log.file} + ${bundle:application:audit.trail.log.file} + ${bundle:application:audit.trail.log.file.pattern} ${bundle:application:logging.level.org.apache.shiro} ${bundle:application:logging.level.org.ohdsi} ${bundle:application:logging.level.org.pac4j} @@ -12,7 +13,7 @@ + filePattern="${audit.trail.log.file.pattern}"> %m%n