diff --git a/src/main/java/org/wildfly/security/audit/PeriodicRotatingFileAuditEndpoint.java b/src/main/java/org/wildfly/security/audit/PeriodicRotatingFileAuditEndpoint.java
new file mode 100644
index 00000000000..35168e9dba7
--- /dev/null
+++ b/src/main/java/org/wildfly/security/audit/PeriodicRotatingFileAuditEndpoint.java
@@ -0,0 +1,226 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2017 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * 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.wildfly.security.audit;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalAdjusters;
+import java.time.temporal.WeekFields;
+import java.util.Date;
+import java.util.Locale;
+
+import static org.wildfly.common.Assert.checkNotNullParam;
+import static org.wildfly.security._private.ElytronMessages.audit;
+
+/**
+ * An audit endpoint which rotates the log at a preset time interval.
+ *
+ * Based on {@link org.jboss.logmanager.handlers.PeriodicSizeRotatingFileHandler}.
+ *
+ * @author Jan Kalina
+ * @author James R. Perkins
+ * @author Yeray Borges
+ */
+public class PeriodicRotatingFileAuditEndpoint extends FileAuditEndpoint {
+
+ private final DateTimeFormatter format;
+ private final Period period;
+ private final ZoneId timeZone;
+ private long nextRollover = Long.MAX_VALUE;
+ private String nextSuffix;
+
+ PeriodicRotatingFileAuditEndpoint(Builder builder) throws IOException {
+ super(builder);
+ this.format = builder.format;
+ this.period = builder.period;
+ this.timeZone = builder.timeZone;
+
+ final File file = getFile();
+ calcNextRollover(file != null && file.lastModified() > 0 ? file.lastModified() : System.currentTimeMillis());
+ }
+
+ @Override
+ protected void preWrite(Date date) {
+ final long recordMillis = date.getTime();
+ if (recordMillis >= nextRollover) {
+ try {
+ final File file = getFile();
+ if (file == null) {
+ // no file is set; a direct output stream or writer was specified
+ return;
+ }
+ closeStreams(); // close the original file (some OSes won't let you move/rename a file that is open)
+ final Path target = file.getParentFile().toPath().resolve(file.getName() + nextSuffix);
+ Files.move(file.toPath(), target, StandardCopyOption.REPLACE_EXISTING);
+ setFile(file);
+ } catch (IOException e) {
+ audit.unableToRotateLogFile(e);
+ }
+ calcNextRollover(recordMillis);
+ }
+ }
+
+ /**
+ * For given time and period obtains time when should be new log file started
+ */
+ private void calcNextRollover(final long fromTime) {
+ if (period == Period.NEVER || format == null) {
+ nextRollover = Long.MAX_VALUE;
+ return;
+ }
+ ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(fromTime), timeZone);
+ nextSuffix = format.format(zonedDateTime);
+ switch (period) {
+ case YEAR:
+ zonedDateTime = zonedDateTime.truncatedTo(ChronoUnit.DAYS)
+ .withDayOfYear(1)
+ .plus(1, ChronoUnit.YEARS);
+ break;
+ case MONTH:
+ zonedDateTime = zonedDateTime.truncatedTo(ChronoUnit.DAYS)
+ .withDayOfMonth(1)
+ .plus(1,ChronoUnit.MONTHS);
+ break;
+ case WEEK:
+ zonedDateTime = zonedDateTime.truncatedTo(ChronoUnit.DAYS)
+ .with(TemporalAdjusters.next(WeekFields.of(Locale.getDefault()).getFirstDayOfWeek()));
+ break;
+ case DAY:
+ zonedDateTime = zonedDateTime.truncatedTo(ChronoUnit.DAYS)
+ .plus(1, ChronoUnit.DAYS);
+ break;
+ case HALF_DAY:
+ ZonedDateTime halfDay = ZonedDateTime.from(zonedDateTime).truncatedTo(ChronoUnit.DAYS)
+ .plus(1, ChronoUnit.HALF_DAYS);
+ if ( zonedDateTime.isBefore(halfDay) ) {
+ zonedDateTime = halfDay;
+ }else{
+ zonedDateTime = halfDay.plus(1, ChronoUnit.HALF_DAYS);
+ }
+ break;
+ case HOUR:
+ zonedDateTime = zonedDateTime.truncatedTo(ChronoUnit.HOURS)
+ .plus(1, ChronoUnit.HOURS);
+ break;
+ case MINUTE:
+ zonedDateTime = zonedDateTime.truncatedTo(ChronoUnit.MINUTES)
+ .plus(1, ChronoUnit.MINUTES);
+ }
+ nextRollover = zonedDateTime.toInstant().toEpochMilli();
+ }
+
+ /**
+ * Possible period values. Keep in strictly ascending order of magnitude.
+ */
+ protected enum Period {
+ MINUTE,
+ HOUR,
+ HALF_DAY,
+ DAY,
+ WEEK,
+ MONTH,
+ YEAR,
+ NEVER,
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder extends FileAuditEndpoint.Builder {
+ DateTimeFormatter format;
+ Period period = Period.NEVER;
+ ZoneId timeZone = ZoneId.systemDefault();
+
+ Builder() {
+ super();
+ }
+
+ /**
+ * Set the configured time zone for this handler.
+ *
+ * @param timeZone the configured time zone
+ * @return this builder.
+ */
+ public Builder setTimeZone(ZoneId timeZone) {
+ this.timeZone = checkNotNullParam("timeZone", timeZone);
+
+ return this;
+ }
+
+ /**
+ * Set the suffix string. The string is in a format which can be understood by {@link java.time.format.DateTimeFormatter}.
+ * The period of the rotation is automatically calculated based on the suffix.
+ * The following characters \/:*?"<>| are always replaced by '_'
+ *
+ * @param suffix the suffix
+ * @throws IllegalArgumentException if the suffix is not valid
+ */
+ public Builder setSuffix(String suffix) throws IllegalArgumentException {
+ format = DateTimeFormatter.ofPattern(suffix.replaceAll("[\\/:*?\"<>|]", "_")).withZone(timeZone);
+ final int len = suffix.length();
+ period = Period.NEVER;
+ for (int i = 0; i < len; i ++) {
+ switch (suffix.charAt(i)) {
+ case 'y': period = min(period, Period.YEAR); break;
+ case 'M': period = min(period, Period.MONTH); break;
+ case 'w':
+ case 'W': period = min(period, Period.WEEK); break;
+ case 'D':
+ case 'd':
+ case 'F':
+ case 'E': period = min(period, Period.DAY); break;
+ case 'a': period = min(period, Period.HALF_DAY); break;
+ case 'H':
+ case 'k':
+ case 'K':
+ case 'h': period = min(period, Period.HOUR); break;
+ case 'm': period = min(period, Period.MINUTE); break;
+ case '\'': while (suffix.charAt(++i) != '\''){} break;
+ case 's':
+ case 'S': throw audit.rotatingBySecondUnsupported(suffix);
+ }
+ }
+
+ return this;
+ }
+
+ /**
+ * Construct a new instance.
+ *
+ * @return the built audit endpoint.
+ * @throws IOException if an I/O error occurs.
+ */
+ @Override
+ public AuditEndpoint build() throws IOException {
+ return new PeriodicRotatingFileAuditEndpoint(this);
+ }
+ }
+
+ private static > T min(T a, T b) {
+ return a.compareTo(b) <= 0 ? a : b;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/wildfly/security/audit/SizeRotatingFileAuditEndpoint.java b/src/main/java/org/wildfly/security/audit/SizeRotatingFileAuditEndpoint.java
new file mode 100644
index 00000000000..0ded051bc0f
--- /dev/null
+++ b/src/main/java/org/wildfly/security/audit/SizeRotatingFileAuditEndpoint.java
@@ -0,0 +1,205 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2017 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * 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.wildfly.security.audit;
+
+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.nio.file.StandardCopyOption;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+
+import static org.wildfly.common.Assert.checkNotNullParam;
+import static org.wildfly.security._private.ElytronMessages.audit;
+
+/**
+ * An audit endpoint which rotates the log at the size of the log.
+ *
+ * Based on {@link org.jboss.logmanager.handlers.PeriodicSizeRotatingFileHandler}.
+ *
+ * @author Jan Kalina
+ * @author James R. Perkins
+ * @author Yeray Borges
+ */
+public class SizeRotatingFileAuditEndpoint extends FileAuditEndpoint {
+ private final long rotateSize;
+ private final int maxBackupIndex;
+ private final boolean rotateOnBoot;
+ private long currentSize = 0;
+ private final String suffix;
+ private final DateTimeFormatter dateTimeFormatter;
+
+ SizeRotatingFileAuditEndpoint(Builder builder) throws IOException {
+ super(builder);
+ this.rotateSize = builder.rotateSize;
+ this.maxBackupIndex = builder.maxBackupIndex;
+ this.rotateOnBoot = builder.rotateOnBoot;
+ this.suffix = builder.suffix;
+ this.dateTimeFormatter = this.suffix != null ? DateTimeFormatter.ofPattern(this.suffix).withZone(builder.timeZone) : null;
+
+ final File file = getFile();
+ if (rotateOnBoot && maxBackupIndex > 0 && file != null && file.exists() && file.length() > 0L) {
+ rotate(file);
+ }
+ }
+
+ @Override
+ protected void write(byte[] bytes) throws IOException {
+ super.write(bytes);
+ currentSize += bytes.length;
+ }
+
+ @Override
+ protected void preWrite(Date date) {
+ if (currentSize > rotateSize && maxBackupIndex > 0) {
+ try {
+ final File file = getFile();
+ if (file == null) {
+ // no file is set; a direct output stream or writer was specified
+ return;
+ }
+ rotate(file);
+ currentSize = 0;
+ } catch (IOException e) {
+ audit.unableToRotateLogFile(e);
+ }
+ }
+ }
+
+ /**
+ * Moves file to file.1, file.1 to file.2 etc. Removes file.{maxBackupIndex}
+ */
+ private void rotate(final File file) throws IOException {
+ closeStreams();
+ final String suffix = dateTimeFormatter != null ? dateTimeFormatter.format(ZonedDateTime.now()) : "";
+ final Path fileWithSuffix = Paths.get(file.getAbsolutePath() + suffix);
+ Files.deleteIfExists(Paths.get(fileWithSuffix + "." + maxBackupIndex));
+ for (int i = maxBackupIndex - 1; i >= 1; i--) {
+ final Path src = Paths.get(fileWithSuffix + "." + i);
+ if (Files.exists(src)) {
+ final Path target = Paths.get(fileWithSuffix + "." + (i + 1));
+ Files.move(src, target, StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+ Files.move(file.toPath(), Paths.get(fileWithSuffix + ".1"), StandardCopyOption.REPLACE_EXISTING);
+ setFile(file);
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder extends FileAuditEndpoint.Builder {
+
+ private long rotateSize = 0xa0000L; // 10 MB by default
+ private int maxBackupIndex = 1;
+ private boolean rotateOnBoot;
+ private String suffix;
+
+ ZoneId timeZone = ZoneId.systemDefault();
+
+ Builder() {
+ super();
+ }
+
+ /**
+ * Set the log file size the file should rotate at.
+ *
+ * @param rotateSize the size the file should rotate at
+ * @return this builder.
+ */
+ public Builder setRotateSize(long rotateSize) {
+ this.rotateSize = rotateSize;
+
+ return this;
+ }
+
+ /**
+ * Sets the suffix to be appended to the file name during the file rotation. The suffix does not play a role in
+ * determining when the file should be rotated.
+ * The following characters \/:*?"<>| are always replaced by '_'
+ *
+ * The suffix must be a string understood by the {@link java.time.format.DateTimeFormatter}.
+ *
+ * Note: Files will be rotated for the same suffix until reach the maximum backup index configured {@see #setMaxBackupIndex(int)}.
+ * If the suffix is resolved to a new value, any files rotated with a different suffix will not be deleted.
+ * For example if the suffix is .yyyy-DD-mm, the maximum size was reached 20 times on the same day and the maxBackupIndex
+ * was set to 10, then there will only be 10 files kept. What will not be purged is files from a previous day.
+ *
+ * @param suffix the suffix to place after the filename when the file is rotated
+ */
+ public Builder setSuffix(String suffix){
+ this.suffix = suffix.replaceAll("[\\/:*?\"<>|]", "_");
+
+ return this;
+ }
+
+ /**
+ * Set the maximum number of files to backup.
+ *
+ * @param maxBackupIndex the maximum number of files to backup
+ * @return this builder.
+ */
+ public Builder setMaxBackupIndex(int maxBackupIndex) {
+ this.maxBackupIndex = maxBackupIndex;
+
+ return this;
+ }
+
+ /**
+ * Set to a value of {@code true} if the file should be rotated before the a new file is set. The rotation only
+ * happens if the file names are the same and the file has a {@link java.io.File#length() length} greater than 0.
+ *
+ * @param rotateOnBoot {@code true} to rotate on boot, otherwise {@code false}
+ * @return this builder.
+ */
+ public SizeRotatingFileAuditEndpoint.Builder setRotateOnBoot(boolean rotateOnBoot) {
+ this.rotateOnBoot = rotateOnBoot;
+
+ return this;
+ }
+
+ /**
+ * Set the configured time zone for this handler.
+ *
+ * @param timeZone the configured time zone
+ * @return this builder.
+ */
+ public SizeRotatingFileAuditEndpoint.Builder setTimeZone(ZoneId timeZone) {
+ this.timeZone = checkNotNullParam("timeZone", timeZone);
+
+ return this;
+ }
+
+ /**
+ * Construct a new instance.
+ *
+ * @return the built audit endpoint.
+ * @throws IOException if an I/O error occurs.
+ */
+
+ @Override
+ public AuditEndpoint build() throws IOException {
+ return new SizeRotatingFileAuditEndpoint(this);
+ }
+ }
+}
diff --git a/src/test/java/org/wildfly/security/audit/PeriodicRotatingFileAuditEndpointTest.java b/src/test/java/org/wildfly/security/audit/PeriodicRotatingFileAuditEndpointTest.java
new file mode 100644
index 00000000000..49ef74aa605
--- /dev/null
+++ b/src/test/java/org/wildfly/security/audit/PeriodicRotatingFileAuditEndpointTest.java
@@ -0,0 +1,237 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2017 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * 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.wildfly.security.audit;
+
+import mockit.Mock;
+import mockit.MockUp;
+import mockit.integration.junit4.JMockit;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.temporal.ChronoUnit;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+
+/**
+ * Test case to test {@link PeriodicRotatingFileAuditEndpoint}
+ *
+ * @author Jan Kalina
+ * @author Yeray Borges
+ */
+@RunWith(JMockit.class)
+public class PeriodicRotatingFileAuditEndpointTest {
+ static File logDirFile;
+ static Path logFile;
+ static ZoneId UTC = ZoneId.of("UTC");
+ Instant currentTime;
+
+ @BeforeClass
+ public static void init() throws Exception {
+ Locale.setDefault(Locale.US);
+ logDirFile = new File(PeriodicRotatingFileAuditEndpointTest.class.getResource(".").getFile(), "audit");
+ logFile = logDirFile.toPath().resolve( "audit");
+ }
+
+ @Before
+ public void initDir() {
+ logDirFile.mkdirs();
+ Assert.assertTrue(logDirFile.isDirectory());
+ for (File file : logDirFile.listFiles()) {
+ file.delete();
+ }
+ assertFiles();
+ }
+
+ @Test
+ public void testBase() throws Exception {
+ AuditEndpoint endpoint = PeriodicRotatingFileAuditEndpoint.builder()
+ .setLocation(logFile)
+ .build();
+ endpoint.accept(EventPriority.CRITICAL, "testing log message");
+ endpoint.close();
+ assertFiles("audit");
+ }
+
+ @Test
+ public void testTimeBasedRolloverYear() throws Exception {
+ AuditEndpoint endpoint = PeriodicRotatingFileAuditEndpoint.builder()
+ .setTimeZone(UTC)
+ .setSuffix(".yyyy")
+ .setLocation(logFile)
+ .build();
+ endpoint.accept(EventPriority.CRITICAL, "testing log message 1");
+ currentTime = currentTime.plus(365,ChronoUnit.DAYS);
+ endpoint.accept(EventPriority.CRITICAL, "testing log message 2");
+ currentTime = currentTime.plus(365,ChronoUnit.DAYS);
+ endpoint.accept(EventPriority.CRITICAL, "testing log message 3");
+ endpoint.close();
+ assertFiles("audit", "audit.1970","audit.1971");
+ }
+
+ @Test
+ public void testTimeBasedRolloverMonth() throws Exception {
+ AuditEndpoint endpoint = PeriodicRotatingFileAuditEndpoint.builder()
+ .setTimeZone(UTC)
+ .setSuffix(".yyyy-MM")
+ .setLocation(logFile)
+ .build();
+ endpoint.accept(EventPriority.CRITICAL, "testing log message 1");
+ currentTime = currentTime.plus(32,ChronoUnit.DAYS);
+ endpoint.accept(EventPriority.CRITICAL, "testing log message 2");
+ currentTime = currentTime.plus(32,ChronoUnit.DAYS);
+ endpoint.accept(EventPriority.CRITICAL, "testing log message 3");
+ endpoint.close();
+ assertFiles("audit", "audit.1970-01","audit.1970-02");
+ }
+
+ @Test
+ public void testTimeBasedRolloverWeek() throws Exception {
+ AuditEndpoint endpoint = PeriodicRotatingFileAuditEndpoint.builder()
+ .setTimeZone(UTC)
+ .setSuffix(".yyyy-MM-ww")
+ .setLocation(logFile)
+ .build();
+ //1 January 1970 is a Thursday
+ endpoint.accept(EventPriority.CRITICAL, "testing log message 1");
+ currentTime = currentTime.plus(4,ChronoUnit.DAYS);
+ endpoint.accept(EventPriority.CRITICAL, "testing log message 2");
+ currentTime = currentTime.plus(7,ChronoUnit.DAYS);
+ endpoint.accept(EventPriority.CRITICAL, "testing log message 3");
+ endpoint.close();
+ assertFiles("audit", "audit.1970-01-01","audit.1970-01-02");
+ }
+
+ @Test
+ public void testTimeBasedRolloverDay() throws Exception {
+ AuditEndpoint endpoint = PeriodicRotatingFileAuditEndpoint.builder()
+ .setTimeZone(UTC)
+ .setSuffix(".yyyy-MM-ww-dd")
+ .setLocation(logFile)
+ .build();
+ endpoint.accept(EventPriority.CRITICAL, "testing log message 1");
+ currentTime = currentTime.plus(1,ChronoUnit.DAYS);
+ endpoint.accept(EventPriority.CRITICAL, "testing log message 2");
+ currentTime = currentTime.plus(1,ChronoUnit.DAYS);
+ endpoint.accept(EventPriority.CRITICAL, "testing log message 3");
+ endpoint.close();
+ assertFiles("audit", "audit.1970-01-01-01","audit.1970-01-01-02");
+ }
+
+ @Test
+ public void testTimeBasedRolloverHalfDay() throws Exception {
+ AuditEndpoint endpoint = PeriodicRotatingFileAuditEndpoint.builder()
+ .setTimeZone(UTC)
+ .setSuffix(".yyyy-MM-ww-dd-a")
+ .setLocation(logFile)
+ .build();
+ endpoint.accept(EventPriority.CRITICAL, "testing log message 1");
+ currentTime = currentTime.plus(12,ChronoUnit.HOURS);
+ endpoint.accept(EventPriority.CRITICAL, "testing log message 2");
+ currentTime = currentTime.plus(12,ChronoUnit.HOURS);
+ endpoint.accept(EventPriority.CRITICAL, "testing log message 3");
+ endpoint.close();
+ assertFiles("audit", "audit.1970-01-01-01-AM","audit.1970-01-01-01-PM");
+ }
+
+ @Test
+ public void testTimeBasedRolloverHour() throws Exception {
+ AuditEndpoint endpoint = PeriodicRotatingFileAuditEndpoint.builder()
+ .setTimeZone(UTC)
+ .setSuffix(".yyyy-MM-ww-dd-a-hh")
+ .setLocation(logFile)
+ .build();
+ endpoint.accept(EventPriority.CRITICAL, "testing log message 1");
+ currentTime = currentTime.plus(1,ChronoUnit.HOURS);
+ endpoint.accept(EventPriority.CRITICAL, "testing log message 2");
+ currentTime = currentTime.plus(1,ChronoUnit.HOURS);
+ endpoint.accept(EventPriority.CRITICAL, "testing log message 3");
+ endpoint.close();
+ assertFiles("audit", "audit.1970-01-01-01-AM-12","audit.1970-01-01-01-AM-01");
+ }
+
+ @Test
+ public void testTimeBasedRolloverHour24() throws Exception {
+ AuditEndpoint endpoint = PeriodicRotatingFileAuditEndpoint.builder()
+ .setTimeZone(UTC)
+ .setSuffix(".yyyy-MM-ww-dd-a-HH")
+ .setLocation(logFile)
+ .build();
+ endpoint.accept(EventPriority.CRITICAL, "testing log message 1");
+ currentTime = currentTime.plus(1,ChronoUnit.HOURS);
+ endpoint.accept(EventPriority.CRITICAL, "testing log message 2");
+ currentTime = currentTime.plus(1,ChronoUnit.HOURS);
+ endpoint.accept(EventPriority.CRITICAL, "testing log message 3");
+ endpoint.close();
+ assertFiles("audit", "audit.1970-01-01-01-AM-00","audit.1970-01-01-01-AM-01");
+ }
+
+ @Test
+ public void testTimeBasedRolloverMinutes() throws Exception {
+ AuditEndpoint endpoint = PeriodicRotatingFileAuditEndpoint.builder()
+ .setTimeZone(UTC)
+ .setSuffix(".yyyy-MM-ww-dd-a-HH:mm")
+ .setLocation(logFile)
+ .build();
+ endpoint.accept(EventPriority.CRITICAL, "testing log message 1");
+ currentTime = currentTime.plus(1,ChronoUnit.MINUTES);
+ endpoint.accept(EventPriority.CRITICAL, "testing log message 2");
+ currentTime = currentTime.plus(1,ChronoUnit.MINUTES);
+ endpoint.accept(EventPriority.CRITICAL, "testing log message 3");
+ endpoint.close();
+ assertFiles("audit", "audit.1970-01-01-01-AM-00_00","audit.1970-01-01-01-AM-00_01");
+ }
+
+ @Before
+ public void mockTime() {
+ currentTime = Instant.EPOCH.truncatedTo(ChronoUnit.DAYS);
+ new MockUp() {
+ @Mock
+ public long currentTimeMillis() {
+ return currentTime.toEpochMilli();
+ }
+ };
+ new MockUp() {
+ @Mock
+ public long lastModified() {
+ return currentTime.toEpochMilli();
+ }
+ };
+ }
+
+ private void assertFiles(String...files) {
+ Set expected = new HashSet<>(Arrays.asList(files));
+ for (File file : logDirFile.listFiles()) {
+ if (! expected.remove(file.getName())) {
+ Assert.fail("Unexpected file "+file.getName());
+ }
+ }
+ for (String missing : expected) {
+ Assert.fail("Missing file "+missing);
+ }
+ }
+}
diff --git a/src/test/java/org/wildfly/security/audit/SizeRotatingFileAuditEndpointTest.java b/src/test/java/org/wildfly/security/audit/SizeRotatingFileAuditEndpointTest.java
new file mode 100644
index 00000000000..a659fd412c4
--- /dev/null
+++ b/src/test/java/org/wildfly/security/audit/SizeRotatingFileAuditEndpointTest.java
@@ -0,0 +1,158 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2017 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * 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.wildfly.security.audit;
+
+import mockit.Mock;
+import mockit.MockUp;
+import mockit.integration.junit4.JMockit;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.temporal.ChronoUnit;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Test case to test {@link SizeRotatingFileAuditEndpointTest}
+ *
+ * @author Jan Kalina
+ * @author Yeray Borges
+ */
+@RunWith(JMockit.class)
+public class SizeRotatingFileAuditEndpointTest {
+ static File logDirFile;
+ static Path logFile;
+ static ZoneId UTC = ZoneId.of("UTC");
+ Instant currentTime;
+
+ @BeforeClass
+ public static void init() throws Exception {
+ logDirFile = new File(SizeRotatingFileAuditEndpointTest.class.getResource(".").getFile(), "audit");
+ logFile = Paths.get(logDirFile.getPath(), "audit");
+ }
+
+ @Test
+ public void testBase() throws Exception {
+ AuditEndpoint endpoint = SizeRotatingFileAuditEndpoint.builder()
+ .setLocation(logFile)
+ .build();
+ endpoint.accept(EventPriority.CRITICAL, "testing log message");
+ endpoint.close();
+ assertFiles("audit");
+ }
+
+ @Test
+ public void testRotateOnSizeOverflow() throws Exception {
+ AuditEndpoint endpoint = SizeRotatingFileAuditEndpoint.builder()
+ .setTimeZone(UTC)
+ .setMaxBackupIndex(4)
+ .setRotateSize(60)
+ .setSuffix(".yyyy-MM-dd")
+ .setLocation(logFile)
+ .build();
+ int i = 0;
+ for (;i < 15; i++) {
+ endpoint.accept(EventPriority.CRITICAL, "testing log message "+i);
+ }
+ currentTime = currentTime.plus(1, ChronoUnit.DAYS);
+ for (;i < 30; i++) {
+ endpoint.accept(EventPriority.CRITICAL, "testing log message "+i);
+ }
+ endpoint.close();
+ assertFiles("audit", "audit.1970-01-01.1", "audit.1970-01-01.2", "audit.1970-01-01.3", "audit.1970-01-01.4",
+ "audit.1970-01-02.1", "audit.1970-01-02.2", "audit.1970-01-02.3", "audit.1970-01-02.4");
+ }
+
+ @Test
+ public void testRotateOnBoot() throws Exception {
+ AuditEndpoint endpoint = SizeRotatingFileAuditEndpoint.builder()
+ .setTimeZone(UTC)
+ .setRotateOnBoot(true)
+ .setMaxBackupIndex(2)
+ .setRotateSize(1)
+ .setSuffix(".yyyy-MM-dd")
+ .setLocation(logFile)
+ .build();
+ endpoint.accept(EventPriority.CRITICAL, "testing log message 1");
+ endpoint.close();
+ endpoint = SizeRotatingFileAuditEndpoint.builder()
+ .setTimeZone(UTC)
+ .setRotateOnBoot(true)
+ .setMaxBackupIndex(2)
+ .setRotateSize(1)
+ .setSuffix(".yyyy-MM-dd")
+ .setLocation(logFile)
+ .build();
+ endpoint.accept(EventPriority.CRITICAL, "testing log message 2");
+ endpoint.close();
+ assertFiles("audit", "audit.1970-01-01.1");
+ }
+
+ @Before
+ public void initDir() {
+ logDirFile.mkdirs();
+ Assert.assertTrue(logDirFile.isDirectory());
+ File[] var1 = logDirFile.listFiles();
+ int var2 = var1.length;
+
+ for(int var3 = 0; var3 < var2; ++var3) {
+ File file = var1[var3];
+ file.delete();
+ }
+
+ this.assertFiles(new String[0]);
+ }
+
+ @Before
+ public void mockTime() {
+ currentTime = Instant.EPOCH.truncatedTo(ChronoUnit.DAYS);
+ new MockUp() {
+ @Mock
+ public long currentTimeMillis() {
+ return currentTime.toEpochMilli();
+ }
+ };
+ new MockUp() {
+ @Mock
+ public long lastModified() {
+ return currentTime.toEpochMilli();
+ }
+ };
+ }
+
+ private void assertFiles(String...files) {
+ Set expected = new HashSet<>(Arrays.asList(files));
+ for (File file : logDirFile.listFiles()) {
+ if (! expected.remove(file.getName())) {
+ Assert.fail("Unexpected file "+file.getName());
+ }
+ }
+ for (String missing : expected) {
+ Assert.fail("Missing file "+missing);
+ }
+ }
+}
\ No newline at end of file