Index: bin/user.properties
===================================================================
--- bin/user.properties (revision 1676359)
+++ bin/user.properties (working copy)
@@ -62,3 +62,20 @@
# Enable Proxy request debug
#log_level.jmeter.protocol.http.proxy.HttpRequestHdr=DEBUG
+
+#---------------------------------------------------------------------------
+# JMX Backup configuration
+#---------------------------------------------------------------------------
+#Enable backups of the .jmx file when the test plan is saved.
+#When enabled, before the .jmx is saved, it will be backed up to a subfolder whose name is built
+#from the .jmx file and suffixed by .backups, ex. test-plan.jmx.backups
+#Backup file names are suffixed by their save date, ex: test-plan.jmx.20150501.150027
+#jmeter.gui.action.save.backup_when_saving=true
+
+#Set the maximum time (in hours) the backup files should be preserved since the most recent save time.
+#By default no expiration time is set which means we keep backups for ever.
+#jmeter.gui.action.save.keep_backup_max_hours=0
+
+#Set the maximum number of backup files that should be preserved. By default 10 backups will be kept.
+#Setting this to zero will cause the backups to not being deleted (unless keep_backup_max_hours is set to a non nul value)
+#jmeter.gui.action.save.keep_backup_max_count=10
Index: src/core/org/apache/jmeter/gui/action/Save.java
===================================================================
--- src/core/org/apache/jmeter/gui/action/Save.java (revision 1676360)
+++ src/core/org/apache/jmeter/gui/action/Save.java (working copy)
@@ -21,15 +21,28 @@
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.FileOutputStream;
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
+import java.util.List;
import java.util.Set;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
+import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.io.filefilter.IOFileFilter;
+import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.control.gui.TestFragmentControllerGui;
import org.apache.jmeter.engine.TreeCloner;
import org.apache.jmeter.exceptions.IllegalUserActionException;
@@ -56,6 +69,12 @@
public class Save implements Command {
private static final Logger log = LoggingManager.getLoggerForClass();
+ private static final String JMX_BACKUP_WHEN_SAVING = "jmeter.gui.action.save.backup_when_saving";
+
+ private static final String JMX_BACKUP_MAX_HOURS = "jmeter.gui.action.save.keep_backup_max_hours";
+
+ private static final String JMX_BACKUP_MAX_COUNT = "jmeter.gui.action.save.keep_backup_max_count";
+
public static final String JMX_FILE_EXTENSION = ".jmx"; // $NON-NLS-1$
private static final Set
+ * Create a backup copy of the specified file whose name will be formed as
+ * follows:
+ * Backup is controlled by the following user properties :
+ * {file_name}.{date_as_yyyyMMdd-HHmmss}
+ *
+ * The backup copy will reside in a subfolder whose name will be the
+ * specified file name suffixed by .backups.
+ *
+ * jmeter.gui.action.save.backup_when_saving : (true|false) Enables or Disables backup
+ * jmeter.gui.action.save.keep_backup_max_hours : (int) Number of hours to keep backups since the most recent save time
+ * jmeter.gui.action.save.keep_backup_max_count : (int) Max number of backups to keep
+ *
user.properties
+ */
+ private List
+ * Filter to be used with commons-io that select files whose names matches
+ * the following format:
+ * {prefix}{separator}{date_format_suffix}
+ *
+ * Example: test-plan.jmx.20150101.152014
+ *
{prefix}{separator}{date_suffix}
+ *
+ * @param fileNamePrefix
+ * Filename prefix to be searched
+ * @param suffixSeparator
+ * Character used as the suffix separator
+ * @param suffixDateFormat
+ * The {@link DateFormat} to be used to elect files whose
+ * suffix are parseable by that {@link DateFormat}
+ */
+ public PrivateDateSuffixFileFilter(String fileNamePrefix, char suffixSeparator, DateFormat suffixDateFormat) {
+ if (StringUtils.isEmpty(fileNamePrefix)) {
+ throw new IllegalArgumentException("baseFileName cannot be null or empty !"); //$NON-NLS-1$
+ }
+ if (suffixDateFormat == null) {
+ throw new IllegalArgumentException("suffixDateFormat cannot be null !"); //$NON-NLS-1$
+ }
+ this.fileNamePrefix = fileNamePrefix;
+ this.suffixDateFormat = suffixDateFormat;
+ this.suffixSeparator = suffixSeparator;
+ }
+
+ @Override
+ public boolean accept(File dir, String name) {
+ if(name.equals(fileNamePrefix)) {
+ return false;
+ }
+ boolean accept = false;
+ int prefixIdx = name.indexOf(fileNamePrefix);
+ // ensure file name starts with the good prefix and that it is
+ // longer
+ if (prefixIdx == 0 && name.length() > fileNamePrefix.length()) {
+ // look for suffix
+ String suffix = name.substring(fileNamePrefix.length());
+ // first char after prefix must be the suffix separator
+ if (suffix.charAt(0) == suffixSeparator && suffix.length() > 1) {
+ try {
+ // check that suffix matches the date format
+ suffixDateFormat.parse(suffix.substring(1));
+ accept = true;
+ } catch (ParseException pe) {
+ // suffix is not of the right format
+ accept = false;
+ }
+ } else {
+ // suffix separator is wrong
+ accept = false;
+ }
+ } else {
+ // file name does not start with the baseFileName
+ accept = false;
+ }
+ return accept;
+ }
+
+ @Override
+ public boolean accept(File file) {
+ return accept(file.getParentFile(), file.getName());
+ }
+ }
}