Index: jspwiki-war/src/main/java/org/apache/wiki/WikiPage.java
===================================================================
--- jspwiki-war/src/main/java/org/apache/wiki/WikiPage.java	(revision 1622810)
+++ jspwiki-war/src/main/java/org/apache/wiki/WikiPage.java	(working copy)
@@ -60,14 +60,20 @@
     /** A special variable name for storing a page alias. */
     public static final String ALIAS = "alias";
     
-    /** A special variable name for storing a redirect note */
-    public static final String REDIRECT = "redirect";
-
-    /** A special variable name for storing a changenote. */
-    public static final String CHANGENOTE = "changenote";
-    
-    private Acl m_accessList = null;
-    
+    /** A special variable name for storing a redirect note */
+    public static final String REDIRECT = "redirect";
+
+    /** A special variable name for storing the author. */
+    public static final String AUTHOR = "author";
+    
+    /** A special variable name for storing a changenote. */
+    public static final String CHANGENOTE = "changenote";
+
+    /** A special variable name for storing a viewcount. */
+    public static final String VIEWCOUNT = "viewcount";
+    
+    private Acl m_accessList = null;
+    
     /**
      *  Create a new WikiPage using a given engine and name.
      *  
Index: jspwiki-war/src/main/java/org/apache/wiki/providers/AbstractFileProvider.java
===================================================================
--- jspwiki-war/src/main/java/org/apache/wiki/providers/AbstractFileProvider.java	(revision 1622810)
+++ jspwiki-war/src/main/java/org/apache/wiki/providers/AbstractFileProvider.java	(working copy)
@@ -31,11 +31,14 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Date;
+import java.util.Enumeration;
 import java.util.List;
+import java.util.Map;
 import java.util.Properties;
 import java.util.TreeSet;
 
 import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
 import org.apache.log4j.Logger;
 import org.apache.wiki.InternalWikiException;
 import org.apache.wiki.WikiEngine;
@@ -72,8 +75,31 @@
     protected String m_encoding;
     
     protected WikiEngine m_engine;
+    
+    public static final String PROP_CUSTOMPROP_MAXLIMIT = "custom.pageproperty.max.allowed";
+    public static final String PROP_CUSTOMPROP_MAXKEYLENGTH = "custom.pageproperty.key.length";
+    public static final String PROP_CUSTOMPROP_MAXVALUELENGTH = "custom.pageproperty.value.length";
 
+    public static final int DEFAULT_MAX_PROPLIMIT = 200;
+    public static final int DEFAULT_MAX_PROPKEYLENGTH = 255;
+    public static final int DEFAULT_MAX_PROPVALUELENGTH = 4096;
+
     /**
+     * This parameter limits the number of custom page properties allowed on a page
+     */
+    public static int MAX_PROPLIMIT = DEFAULT_MAX_PROPLIMIT;
+    /**
+     * This number limits the length of a custom page property key length
+     * The default value here designed with future JDBC providers in mind.
+     */
+    public static int MAX_PROPKEYLENGTH = DEFAULT_MAX_PROPKEYLENGTH;
+    /**
+     * This number limits the length of a custom page property value length
+     * The default value here designed with future JDBC providers in mind.
+     */
+    public static int MAX_PROPVALUELENGTH = DEFAULT_MAX_PROPVALUELENGTH;
+
+    /**
      *  Name of the property that defines where page directories are.
      */
     public static final String      PROP_PAGEDIR = "jspwiki.fileSystemProvider.pageDir";
@@ -136,6 +162,12 @@
             m_windowsHackNeeded = true;
         }
         
+    	if (properties != null) {
+            MAX_PROPLIMIT = TextUtil.getIntegerProperty(properties,PROP_CUSTOMPROP_MAXLIMIT,DEFAULT_MAX_PROPLIMIT);
+            MAX_PROPKEYLENGTH = TextUtil.getIntegerProperty(properties,PROP_CUSTOMPROP_MAXKEYLENGTH,DEFAULT_MAX_PROPKEYLENGTH);
+            MAX_PROPVALUELENGTH = TextUtil.getIntegerProperty(properties,PROP_CUSTOMPROP_MAXVALUELENGTH,DEFAULT_MAX_PROPVALUELENGTH);
+    	}
+        
         log.info( "Wikipages are read from '" + m_pageDirectory + "'" );
     }
 
@@ -514,6 +546,95 @@
     }
 
     /**
+     * Set the custom properties provided into the given page.
+     * 
+     * @since 2.10.2
+     */
+    protected void setCustomProperties(WikiPage page, Properties properties) {
+        Enumeration propertyNames = properties.propertyNames();
+    	while (propertyNames.hasMoreElements()) {
+    		String key = (String) propertyNames.nextElement();
+    		if (!key.equals(WikiPage.AUTHOR) && !key.equals(WikiPage.CHANGENOTE) && !key.equals(WikiPage.VIEWCOUNT)) {
+    			page.setAttribute(key, properties.get(key));
+    		}
+    	}
+    }
+
+    /**
+     * Get custom properties using {@link this.addCustomPageProperties}, validate them using {@link this.validateCustomPageProperties}
+     * and add them to default properties provided
+     * 
+     * @since 2.10.2
+     */
+    protected void getCustomProperties(WikiPage page, Properties defaultProperties) throws IOException {
+        Properties customPageProperties = addCustomProperties(page,defaultProperties);
+    	validateCustomPageProperties(customPageProperties);
+    	defaultProperties.putAll(customPageProperties);
+    }
+    
+    /**
+     * By default all page attributes that start with "@" are returned as custom properties.
+     * This can be overwritten by custom FileSystemProviders to save additional properties.
+     * CustomPageProperties are validated by {@link this.validateCustomPageProperties}
+     * 
+     * @since 2.10.2
+     * @param page the current page
+     * @param props the default properties of this page
+     * @return default implementation returns empty Properties. 
+     */
+    protected Properties addCustomProperties(WikiPage page, Properties props) {
+    	Properties customProperties = new Properties();
+    	if (page != null) {
+    		Map<String,Object> atts = page.getAttributes();
+    		for (String key : atts.keySet()) {
+    			Object value = atts.get(key);
+    			if (key.startsWith("@") && value != null) {
+    				customProperties.put(key,value.toString());
+    			}
+    		}
+    		
+    	}
+    	return customProperties;
+    }
+    
+    /**
+     * Default validation, validates that key and value is ASCII <code>StringUtils.isAsciiPrintable()</code> and within lengths set up in jspwiki-custom.properties.
+     * This can be overwritten by custom FileSystemProviders to validate additional properties
+     * See https://issues.apache.org/jira/browse/JSPWIKI-856
+     * @since 2.10.2
+     * @param customProperties the custom page properties being added
+     */
+    protected void validateCustomPageProperties(Properties customProperties) throws IOException {
+    	// Default validation rules
+    	if (customProperties != null && !customProperties.isEmpty()) {
+    		if (customProperties.size()>MAX_PROPLIMIT) {
+    			throw new IOException("Too many custom properties. You are adding "+customProperties.size()+", but max limit is "+MAX_PROPLIMIT);
+    		}
+            Enumeration propertyNames = customProperties.propertyNames();
+        	while (propertyNames.hasMoreElements()) {
+        		String key = (String) propertyNames.nextElement();
+        		String value = (String)customProperties.get(key);
+    			if (key != null) {
+    				if (key.length()>MAX_PROPKEYLENGTH) {
+    					throw new IOException("Custom property key "+key+" is too long. Max allowed length is "+MAX_PROPKEYLENGTH);
+    				}
+    				if (!StringUtils.isAsciiPrintable(key)) {
+    					throw new IOException("Custom property key "+key+" is not simple ASCII!");
+    				}
+    			}
+    			if (value != null) {
+    				if (value.length()>MAX_PROPVALUELENGTH) {
+						throw new IOException("Custom property key "+key+" has value that is too long. Value="+value+". Max allowed length is "+MAX_PROPVALUELENGTH);
+					}
+    				if (!StringUtils.isAsciiPrintable(value)) {
+    					throw new IOException("Custom property key "+key+" has value that is not simple ASCII! Value="+value);
+    				}
+    			}
+        	}
+    	}
+    }
+
+    /**
      *  A simple filter which filters only those filenames which correspond to the
      *  file extension used.
      */
Index: jspwiki-war/src/main/java/org/apache/wiki/providers/FileSystemProvider.java
===================================================================
--- jspwiki-war/src/main/java/org/apache/wiki/providers/FileSystemProvider.java	(revision 1622810)
+++ jspwiki-war/src/main/java/org/apache/wiki/providers/FileSystemProvider.java	(working copy)
@@ -15,17 +15,22 @@
     KIND, either express or implied.  See the License for the
     specific language governing permissions and limitations
     under the License.  
- */
-package org.apache.wiki.providers;
-
-import java.io.*;
-import java.util.Properties;
-
-import org.apache.log4j.Logger;
-import org.apache.wiki.*;
-import org.apache.wiki.api.exceptions.ProviderException;
-
-/**
+ */
+package org.apache.wiki.providers;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Properties;
+
+import org.apache.log4j.Logger;
+import org.apache.wiki.WikiPage;
+import org.apache.wiki.api.exceptions.ProviderException;
+
+/**
  *  Provides a simple directory based repository for Wiki pages.
  *  <P>
  *  All files have ".txt" appended to make life easier for those
@@ -69,23 +74,33 @@
         OutputStream out = null;
 
         try
-        {
-            String author = page.getAuthor();
-            String changenote = (String)page.getAttribute( WikiPage.CHANGENOTE );
-            
-            if( author != null )
-            {
-                props.setProperty( "author", author );
-            }
-            
-            if( changenote != null )
-            {
-                props.setProperty( "changenote", changenote );
-            }
-            
-            File file = new File( getPageDirectory(), 
-                                  mangleName(page.getName())+PROP_EXT );
-     
+        {
+            String author = page.getAuthor();
+            String changenote = (String)page.getAttribute( WikiPage.CHANGENOTE );
+            String viewcount = (String)page.getAttribute( WikiPage.VIEWCOUNT );
+            
+            if( author != null )
+            {
+                props.setProperty( WikiPage.AUTHOR, author );
+            }
+            
+            if( changenote != null )
+            {
+                props.setProperty( WikiPage.CHANGENOTE, changenote );
+            }
+
+            if( viewcount != null )
+            {
+                props.setProperty( WikiPage.VIEWCOUNT, viewcount );
+            }
+            
+            // Get additional custom properties from page and add to props
+            getCustomProperties(page, props);
+            	
+            
+            File file = new File( getPageDirectory(), 
+                                  mangleName(page.getName())+PROP_EXT );
+     
             out = new FileOutputStream( file );
 
             props.store( out, "JSPWiki page properties for page "+page.getName() );
@@ -92,13 +107,13 @@
         }
         finally
         {
-            if( out != null ) out.close();
-        }
-    }
-
-    /**
-     *  Gets basic metadata from file.
-     */
+            if( out != null ) out.close();
+        }
+    }
+    
+    /**
+     *  Gets basic metadata from file.
+     */
     private void getPageProperties( WikiPage page )
         throws IOException
     {
@@ -113,19 +128,28 @@
             if( file.exists() )
             {
                 in = new FileInputStream( file );
-
-                props.load(in);
-
-                page.setAuthor( props.getProperty( "author" ) );
-                
-                String changenote = props.getProperty( "changenote" );
-                if( changenote != null )
-                {
-                    page.setAttribute( WikiPage.CHANGENOTE, changenote );
-                }
-            }            
-        }
-        finally
+
+                props.load(in);
+
+                page.setAuthor( props.getProperty( WikiPage.AUTHOR ) );
+                
+                String changenote = props.getProperty( WikiPage.CHANGENOTE );
+                if( changenote != null )
+                {
+                    page.setAttribute( WikiPage.CHANGENOTE, changenote );
+                }
+                
+                String viewcount = props.getProperty( WikiPage.VIEWCOUNT );
+                if( viewcount != null )
+                {
+                    page.setAttribute( WikiPage.VIEWCOUNT, viewcount );
+                }
+                
+                // Set the props values to the page attributes
+                setCustomProperties(page, props);
+            }            
+        }
+        finally
         {
             if( in != null ) in.close();
         }
Index: jspwiki-war/src/main/java/org/apache/wiki/providers/VersioningFileProvider.java
===================================================================
--- jspwiki-war/src/main/java/org/apache/wiki/providers/VersioningFileProvider.java	(revision 1622810)
+++ jspwiki-war/src/main/java/org/apache/wiki/providers/VersioningFileProvider.java	(working copy)
@@ -514,12 +514,15 @@
             String changeNote = (String) page.getAttribute(WikiPage.CHANGENOTE);
             if( changeNote != null )
             {
-                props.setProperty( versionNumber+".changenote", changeNote );
-            }
-
-            putPageProperties( page.getName(), props );
-        }
-        catch( IOException e )
+                props.setProperty( versionNumber+".changenote", changeNote );
+            }
+
+            // Get additional custom properties from page and add to props
+            getCustomProperties(page, props);
+            
+            putPageProperties( page.getName(), props );
+        }
+        catch( IOException e )
         {
             log.error( "Saving failed", e );
             throw new ProviderException("Could not save page text: "+e.getMessage());
@@ -596,22 +599,24 @@
                 String author = props.getProperty( realVersion+".author" );
                 if ( author == null )
                 {
-                    // we might not have a versioned author because the
-                    // old page was last maintained by FileSystemProvider
-                    Properties props2 = getHeritagePageProperties( page );
-                    author = props2.getProperty( "author" );
-                }
-                if ( author != null )
-                {
+                    // we might not have a versioned author because the
+                    // old page was last maintained by FileSystemProvider
+                    Properties props2 = getHeritagePageProperties( page );
+                    author = props2.getProperty( WikiPage.AUTHOR );
+                }
+                if ( author != null )
+                {
                     p.setAuthor( author );
                 }
 
-                String changenote = props.getProperty( realVersion+".changenote" );
-                if( changenote != null ) p.setAttribute( WikiPage.CHANGENOTE, changenote );
-
-            }
-            catch( IOException e )
-            {
+                String changenote = props.getProperty( realVersion+".changenote" );
+                if( changenote != null ) p.setAttribute( WikiPage.CHANGENOTE, changenote );
+
+                // Set the props values to the page attributes
+                setCustomProperties(p, props);
+            }
+            catch( IOException e )
+            {
                 log.error( "Cannot get author for page"+page+": ", e );
             }
         }
@@ -695,13 +700,13 @@
                 in = new BufferedInputStream(
                             new FileInputStream( propertyFile ));
 
-                Properties props = new Properties();
-                props.load(in);
-
-                String originalAuthor = props.getProperty("author");
-                if ( originalAuthor.length() > 0 )
-                {
-                    // simulate original author as if already versioned
+                Properties props = new Properties();
+                props.load(in);
+
+                String originalAuthor = props.getProperty(WikiPage.AUTHOR);
+                if ( originalAuthor.length() > 0 )
+                {
+                    // simulate original author as if already versioned
                     // but put non-versioned property in special cache too
                     props.setProperty( "1.author", originalAuthor );
 
Index: jspwiki-war/src/main/resources/ini/jspwiki.properties
===================================================================
--- jspwiki-war/src/main/resources/ini/jspwiki.properties	(revision 1622810)
+++ jspwiki-war/src/main/resources/ini/jspwiki.properties	(working copy)
@@ -231,6 +231,17 @@
 jspwiki.encoding = UTF-8
 
 #
+# The following 3 properties apply sensible constraints around custom page
+# properties that can be saved into the AbstractFileProvider. These default
+# values were chosen with future JDBC providers in mind.
+# See: https://issues.apache.org/jira/browse/JSPWIKI-856
+#
+#custom.pageproperty.max.allowed=200
+#custom.pageproperty.key.length=255
+#custom.pageproperty.value.length=4096
+
+
+#
 #  Determines whether raw HTML is allowed as Wiki input.
 #
 #  THIS IS A DANGEROUS OPTION!
Index: jspwiki-war/src/test/java/org/apache/wiki/providers/FileSystemProviderTest.java
===================================================================
--- jspwiki-war/src/test/java/org/apache/wiki/providers/FileSystemProviderTest.java	(revision 1622810)
+++ jspwiki-war/src/test/java/org/apache/wiki/providers/FileSystemProviderTest.java	(working copy)
@@ -266,11 +266,38 @@
         
         f = new File( files, "Test.properties" );
         
-        assertFalse( "properties exist", f.exists() );
+        assertFalse( "properties exist", f.exists() );
+    }
+
+    public void testCustomProperties() throws Exception {
+        String pageDir = props.getProperty( FileSystemProvider.PROP_PAGEDIR );
+        String pageName = "CustomPropertiesTest";
+        String fileName = pageName+FileSystemProvider.FILE_EXT;
+        File file = new File (pageDir,fileName);
+
+        assertFalse( file.exists() );
+        WikiPage testPage = new WikiPage(m_engine,pageName);
+        testPage.setAuthor("TestAuthor");
+        testPage.setAttribute("@test","Save Me");
+        testPage.setAttribute("@test2","Save You");
+        testPage.setAttribute("test3","Do not save");
+        m_provider.putPageText( testPage, "This page has custom properties" );
+        assertTrue("No such file", file.exists() );
+        WikiPage pageRetrieved = m_provider.getPageInfo( pageName, -1 );
+        String value = (String)pageRetrieved.getAttribute("@test");
+        String value2 = (String)pageRetrieved.getAttribute("@test2");
+        String value3 = (String)pageRetrieved.getAttribute("test3");
+        assertNotNull(value);
+        assertNotNull(value2);
+        assertNull(value3);
+        assertEquals("Save Me",value);
+        assertEquals("Save You",value2);
+        file.delete();
+        assertFalse( file.exists() );
+    }
+
+    public static Test suite()
+    {
+        return new TestSuite( FileSystemProviderTest.class );
     }
-
-    public static Test suite()
-    {
-        return new TestSuite( FileSystemProviderTest.class );
-    }
 }
