From 26eb9e3805ae51eed7f4f8917a2c0bd7275dad4a Tue, 26 Oct 2010 09:12:00 +0200
From: marijan milicevic <m.milicevic@onehippo.com>
Date: Tue, 26 Oct 2010 10:12:29 +0200
Subject: [PATCH] - added multivalues support for hst formmap data
 - added multiple (error) messages per field
 - added seal data option (once sealed, form data cannot be read anymore). Useful when finializing form
 - added simple exmaple

---
 .../support/forms/BaseFormHstComponent.java        |  141 ++++---
 .../hst/component/support/forms/FormField.java     |  121 +++++
 .../hst/component/support/forms/FormMap.java       |  146 ++++--
 .../hst/component/support/forms/FormFieldTest.java |   24 +
 .../hstconfiguration/democommon/components.xml     |   19 +
 .../hstconfiguration/democommon/pages.xml          |   50 ++-
 .../hstconfiguration/democommon/templates.xml      |    8 +
 .../hstconfiguration/demosite/sitemap.xml          |   12 +-
 .../org/hippoecm/hst/demo/components/HstForm.java  |   45 ++
 .../main/webapp/jsp/components/main/formpage.jsp   |  115 +++++
 .../java/org/hippoecm/hst/components/Contact.java  |    2 +-
 .../org/hippoecm/hst/components/ContactSpring.java |    6 +-
 .../main/webapp/jsp/components/contactdispatch.jsp |    7 +-
 .../cnd/src/main/resources/hst-types.cnd           |  499 ++++++++++----------
 14 files changed, 820 insertions(+), 375 deletions(-)
 create mode 100644 client/src/main/java/org/hippoecm/hst/component/support/forms/FormField.java
 create mode 100644 client/src/test/java/org/hippoecm/hst/component/support/forms/FormFieldTest.java
 create mode 100644 demosuite/site/src/main/java/org/hippoecm/hst/demo/components/HstForm.java
 create mode 100644 demosuite/site/src/main/webapp/jsp/components/main/formpage.jsp

diff --git a/client/src/main/java/org/hippoecm/hst/component/support/forms/BaseFormHstComponent.java b/client/src/main/java/org/hippoecm/hst/component/support/forms/BaseFormHstComponent.java
index 8e33760..e6d705f 100644
--- a/client/src/main/java/org/hippoecm/hst/component/support/forms/BaseFormHstComponent.java
+++ b/client/src/main/java/org/hippoecm/hst/component/support/forms/BaseFormHstComponent.java
@@ -15,19 +15,13 @@
  */
 package org.hippoecm.hst.component.support.forms;
 
-import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.List;
+import java.util.Map;
 import java.util.Random;
 import java.util.Map.Entry;
 
-import javax.jcr.ItemNotFoundException;
-import javax.jcr.LoginException;
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.SimpleCredentials;
-import javax.jcr.Value;
+import javax.jcr.*;
 
 import org.apache.jackrabbit.uuid.UUID;
 import org.hippoecm.hst.component.support.bean.BaseHstComponent;
@@ -41,21 +35,24 @@ import org.slf4j.LoggerFactory;
 public class BaseFormHstComponent extends BaseHstComponent{
 
     static Logger log = LoggerFactory.getLogger(BaseFormHstComponent.class);
-    
+
     public final static String DEFAULT_UUID_NAME = "u_u_i_d";
     public final static String DEFAULT_STORED_FORMS_LOCATION = "formdata";
     public final static String DEFAULT_FORMDATA_CONTAINER = "hst:formdatacontainer";
     public final static String DEFAULT_FORMDATA_TYPE = "hst:formdata";
 
-    public final static String HST_PARAMETERNAMES = "hst:parameternames";
-    public final static String HST_PARAMETERVALUES = "hst:parametervalues";
     public final static String HST_CREATIONTIME =  "hst:creationtime";
     public final static String HST_PREDECESSOR =  "hst:predecessor";
+    public final static String HST_FORM_DATA_NODE =  "hst:formfieldvalue";
+    public static final String HST_FORM_FIELD_DATA = "hst:formfielddata";
+    public static final String HST_FORM_FIELD_MESSAGES = "hst:formfieldmessages";
+    public static final String HST_FORM_FIELD_NAME = "hst:formfieldname";
 
     public final static String DEFAULT_WRITABLE_USERNAME_PROPERTY = "writable.repository.user.name";
     public final static String DEFAULT_WRITABLE_PASSWORD_PROPERTY = "writable.repository.password";
-    
-   
+    public static final String HST_SEALED = "hst:sealed";
+
+
     @Override
     public void doAction(HstRequest request, HstResponse response) throws HstComponentException {
         super.doAction(request, response);
@@ -65,9 +62,9 @@ public class BaseFormHstComponent extends BaseHstComponent{
 
     /**
      * This method tries to repopulate an earlier posted form that was stored in the repository.
-     * 
+     *
      * Only when there is a request parameter containing the correct uuid, you can re-populate it.
-     * 
+     *
      * @param request the current hstRequest
      * @param formMap the formMap that will be populated
      */
@@ -79,31 +76,45 @@ public class BaseFormHstComponent extends BaseHstComponent{
         if(request.getParameter(DEFAULT_UUID_NAME) != null) {
             String uuid = request.getParameter(DEFAULT_UUID_NAME);
             try {
-                // check whether it is a valid uuid. Although uuidObj is not used afterwards, keep it here for parsing the uuid, if it fails
-                // an IllegalArgumentException is thrown
-                @SuppressWarnings("unused")
-                UUID uuidObj = UUID.fromString(uuid);
+                validateId(uuid);
                 Session session = request.getRequestContext().getSession();
                 Node persistedFormData = session.getNodeByUUID(uuid);
-                
+                // check if form is sealed
+                if(persistedFormData.hasProperty(HST_SEALED) && persistedFormData.getProperty(HST_SEALED).getBoolean()){
+                    log.debug("From is sealed, not allowed to read data");
+                    return;
+                }
+
                 if(persistedFormData.hasProperty(HST_PREDECESSOR)) {
                     formMap.setPrevious(persistedFormData.getProperty(HST_PREDECESSOR).getString());
                 }
-                if(persistedFormData.hasProperty(HST_PARAMETERNAMES) && persistedFormData.hasProperty(HST_PARAMETERVALUES)) {
-                    Value[] names =  persistedFormData.getProperty(HST_PARAMETERNAMES).getValues();
-                    Value[] values =  persistedFormData.getProperty(HST_PARAMETERVALUES).getValues();
-                    if(names.length != values.length) {
-                        log.warn("Ambiguous stored formfields because there are unequal names & values. Return unpopulated map");
-                        return;
-                    } else {
-                        for(int i = 0 ; i < names.length ; i ++ ){
-                            formMap.addFormField(names[i].getString(), values[i].getString());
+                // fetch previously stored form field data (nodes)
+                if(persistedFormData.hasNodes()){
+                    NodeIterator fieldIterator = persistedFormData.getNodes(HST_FORM_DATA_NODE);
+                    while (fieldIterator.hasNext()) {
+                        Node fieldNode = fieldIterator.nextNode();
+                        // sanity check (property is mandatory)
+                        if(fieldNode.hasProperty(HST_FORM_FIELD_NAME)){
+                            // create field (even if we do not have values)
+                            String fieldName = fieldNode.getProperty(HST_FORM_FIELD_NAME).getString();
+                            FormField field = new FormField(fieldName);
+                            formMap.addFormField(fieldName, field);
+                            if(fieldNode.hasProperty(HST_FORM_FIELD_DATA)){
+                                Value[] values = fieldNode.getProperty(HST_FORM_FIELD_DATA).getValues();
+                                for (Value value : values) {
+                                    field.addValue(value.getString());
+                                }
+                            }
+                            if(fieldNode.hasProperty(HST_FORM_FIELD_MESSAGES)){
+                                Value[] values = fieldNode.getProperty(HST_FORM_FIELD_MESSAGES).getValues();
+                                for (Value value : values) {
+                                    field.addMessage(value.getString());
+                                }
+                            }
                         }
-                        log.debug("Succesfully repopulated formerly persisted form");
-                       
                     }
                 }
-                return;
+
             } catch(IllegalArgumentException e){
                 log.warn("Not a valid uuid. Return");
             } catch (LoginException e) {
@@ -113,13 +124,13 @@ public class BaseFormHstComponent extends BaseHstComponent{
             } catch (RepositoryException e) {
                 log.warn("RepositoryException '{}'. Return" , e.getMessage());
             }
-            return;
         } else {
             log.debug("No uuid in request parameter. No form to populate");
-            return;
         }
     }
-    
+
+
+
     /**
      * Facility to temporarily store submitted form data which needs to be accessed in the rendering phase again. This method
      * add the uuid of the newly created jcr node on the response as a render parameter
@@ -129,8 +140,8 @@ public class BaseFormHstComponent extends BaseHstComponent{
      * @param storeFormResult an object to store some result of the data persisting, for example the uuid of the created node
      * @throws HstComponentException when the storing of the formdata fails
      */
-    
-    protected void persistFormMap(HstRequest request, HstResponse response, FormMap formMap, StoreFormResult storeFormResult) throws HstComponentException { 
+
+    protected void persistFormMap(HstRequest request, HstResponse response, FormMap formMap, StoreFormResult storeFormResult) throws HstComponentException {
             Session impersonated = null;
             try {
                 Session session = request.getRequestContext().getSession();
@@ -150,43 +161,48 @@ public class BaseFormHstComponent extends BaseHstComponent{
                     log.error("Cannot impersonate a session with '{}' and '{}'", username, password);
                     return;
                 }
-                    
+
                 Node rootNode = impersonated.getRootNode();
-                Node formData = null;
+                Node formData;
                 if(!rootNode.hasNode(getFormDataNodeName())) {
                     formData = rootNode.addNode(getFormDataNodeName(), DEFAULT_FORMDATA_CONTAINER);
                     addInitialStructure(formData);
                 } else {
                     formData = rootNode.getNode(getFormDataNodeName());
                 }
-                
+
                 Node randomNode = createRandomNode(formData);
                 Node postedFormDataNode = randomNode.addNode("tick_"+System.currentTimeMillis(), getFormDataNodeType() );
                 postedFormDataNode.setProperty(HST_CREATIONTIME, Calendar.getInstance());
+                postedFormDataNode.setProperty(HST_SEALED, formMap.isSealed());
                 // if there is a previously stored node of this form, set this uuid as predecessor
-                // TODO the  colon ':' is configurable, so must not be hardcode here
+                // TODO the  colon ':' is configurable, so must not be hardcoded here
                 if(request.getParameter(request.getReferenceNamespace()+":"+DEFAULT_UUID_NAME) != null) {
                     postedFormDataNode.setProperty(HST_PREDECESSOR, request.getParameter(request.getReferenceNamespace()+":"+DEFAULT_UUID_NAME));
                 }
 
                 postedFormDataNode.addMixin("mix:referenceable");
-                List<String> names = new ArrayList<String>();
-                List<String> values = new ArrayList<String>();
-                for(Entry<String, String> entry : formMap.getFormMap().entrySet() ) {
-                    names.add(entry.getKey());
-                    String value = entry.getValue() == null ? "" : entry.getValue();
-                    values.add(value);
+
+                for(Entry<String, FormField> entry : formMap.getFormMap().entrySet() ) {
+                    FormField field = entry.getValue();
+                    Node fieldNode = postedFormDataNode.addNode(HST_FORM_DATA_NODE, HST_FORM_DATA_NODE);
+                    fieldNode.setProperty(HST_FORM_FIELD_NAME, field.getName());
+                    Map<String,String> values = field.getValues();
+                    if(values.size() > 0){
+                        fieldNode.setProperty(HST_FORM_FIELD_DATA, values.values().toArray(new String[values.size()]));
+                    }
+                    List<String> messages = field.getMessages();
+                    if(messages.size() > 0){
+                        fieldNode.setProperty(HST_FORM_FIELD_MESSAGES, messages.toArray(new String[messages.size()]));
+                    }
                 }
-                
-                postedFormDataNode.setProperty(HST_PARAMETERNAMES, names.toArray(new String[names.size()]));
-                postedFormDataNode.setProperty(HST_PARAMETERVALUES, values.toArray(new String[values.size()]));
                 rootNode.save();
                 response.setRenderParameter(DEFAULT_UUID_NAME, postedFormDataNode.getUUID());
                 if(storeFormResult != null) {
                     storeFormResult.populateResult(postedFormDataNode);
                 }
             } catch (LoginException e) {
-               throw new HstComponentException("LoginExceptionExeption during storing form data: ", e);
+               throw new HstComponentException("LoginException  during storing form data: ", e);
             } catch (RepositoryException e) {
                throw new HstComponentException("RepositoryException during storing form data: ", e);
             } finally {
@@ -195,7 +211,7 @@ public class BaseFormHstComponent extends BaseHstComponent{
                 }
             }
     }
-    
+
     /**
      * If you have a different property for you writable user instead of the default {@link #DEFAULT_WRITABLE_PASSWORD_PROPERTY}, then override
      * this method
@@ -221,7 +237,7 @@ public class BaseFormHstComponent extends BaseHstComponent{
     protected String getFormDataNodeName(){
         return DEFAULT_STORED_FORMS_LOCATION;
     }
-    
+
     /**
      * Override this method if you need a different node type
      * @return default node type to store form data
@@ -229,8 +245,8 @@ public class BaseFormHstComponent extends BaseHstComponent{
     protected String getFormDataNodeType(){
         return DEFAULT_FORMDATA_TYPE;
     }
-    
-    
+
+
     private void addInitialStructure(Node formData) throws RepositoryException {
         char a = 'a';
         for(int i = 0; i < 26 ; i ++) {
@@ -240,7 +256,7 @@ public class BaseFormHstComponent extends BaseHstComponent{
             }
         }
     }
-    
+
     private Node createRandomNode(Node formData)throws RepositoryException {
         Node result = formData;
         char a = 'a';
@@ -257,6 +273,15 @@ public class BaseFormHstComponent extends BaseHstComponent{
         }
         return result;
     }
-    
-    
+
+    /**
+     * Validates an uuid. If invalid uuid is passed,  IllegalArgumentException exception will be thrown
+     *
+     * @param uuid uuid to validate
+     * @throws IllegalArgumentException thrown on invalid uuid
+     */
+    private void validateId(final String uuid) throws IllegalArgumentException {
+        UUID.fromString(uuid);
+    }
+
 }
diff --git a/client/src/main/java/org/hippoecm/hst/component/support/forms/FormField.java b/client/src/main/java/org/hippoecm/hst/component/support/forms/FormField.java
new file mode 100644
index 0000000..9074516
--- /dev/null
+++ b/client/src/main/java/org/hippoecm/hst/component/support/forms/FormField.java
@@ -0,0 +1,121 @@
+package org.hippoecm.hst.component.support.forms;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Simple value wrapper for form fields (name and data). Supports multiple value fields
+ *
+ * @version $Id$
+ */
+public class FormField {
+    @SuppressWarnings({"UnusedDeclaration"})
+    private static Logger log = LoggerFactory.getLogger(FormField.class);
+
+    public FormField() {
+    }
+
+    public FormField(final String name) {
+        this.name = name;
+    }
+
+    // field name
+    private String name;
+    // stored values
+    private Map<String,String> values;
+    // error messages
+    private List<String> messages;
+
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+
+    public Map<String,String> getValues() {
+        if (values == null) {
+            return Collections.emptyMap();
+        }
+        return values;
+    }
+
+    public void setValues(final Map<String,String> values) {
+        this.values = values;
+    }
+
+    public void addValue(final String value) {
+        if (value == null) {
+            return;
+        }
+        if (values == null) {
+            values = new HashMap<String,String>();
+        }
+        values.put(value,value);
+    }
+
+    /**
+     * Most of the fields have single values, we'll return first element (if there), null otherwise
+     *
+     * @return first value or empty string if no values present
+     */
+    public String getValue() {
+        if (values == null || values.size() == 0) {
+            return "";
+        }
+        return values.values().iterator().next();
+    }
+
+    public List<String> getMessages() {
+        if (messages == null) {
+            return Collections.emptyList();
+        }
+        return messages;
+    }
+
+    public void addMessage(final String value) {
+        if (messages == null) {
+            messages = new ArrayList<String>();
+        }
+        messages.add(value);
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final FormField formField = (FormField) o;
+
+        if (name != null ? !name.equals(formField.name) : formField.name != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Fields should be equal for same names
+     *
+     * @return hash code of the field name
+     */
+    @Override
+    public int hashCode() {
+        return name != null ? name.hashCode() : 0;
+    }
+
+
+}
diff --git a/client/src/main/java/org/hippoecm/hst/component/support/forms/FormMap.java b/client/src/main/java/org/hippoecm/hst/component/support/forms/FormMap.java
index 8560a8a..faa8250 100644
--- a/client/src/main/java/org/hippoecm/hst/component/support/forms/FormMap.java
+++ b/client/src/main/java/org/hippoecm/hst/component/support/forms/FormMap.java
@@ -18,90 +18,146 @@ package org.hippoecm.hst.component.support.forms;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 
 import org.hippoecm.hst.core.component.HstRequest;
 
 /**
- * A simple Form object just holding a fieldname <--> fieldvalue HashMap
- *
+ * A simple Form object just holding a fieldname <--> fields HashMap
  */
 public class FormMap {
 
-    private final static String MESSAGE_PREFIX = "msg-key::";
-    
-    private Map<String,String> formMap = new HashMap<String,String>();
-    private Map<String,String> messages = null;
+
+    private Map<String, FormField> formMap = new HashMap<String, FormField>();
+    // all messages
+    private Map<String, List<String>> messages = null;
+    // first messages
+    private Map<String, String> message = null;
     private String predecessorUUID = null;
-    
-    
-    public FormMap(){
+    private static final String[] EMPTY_STRING_ARRAY = new String[0];
+    // flag indicating form is sealed: sealed form data will not be displayed after subsequent reloads
+    private boolean sealed;
+
+    public FormMap() {
         // empty form
     }
-    public FormMap(HstRequest request, List<String> fieldNames){
+
+    public FormMap(HstRequest request, List<String> fieldNames) {
         // lets populate from
-        this(request,fieldNames.toArray(new String[fieldNames.size()]));
+        this(request, fieldNames.toArray(new String[fieldNames.size()]));
     }
-    
-    public FormMap(HstRequest request, String[] fieldNames){
-        for(String name : fieldNames) {
-            String value = request.getParameter(name) == null ? "" : request.getParameter(name);
-            formMap.put(name, value);
+
+    public FormMap(HstRequest request, String[] fieldNames) {
+        for (String name : fieldNames) {
+            String[] values = request.getParameterValues(name) == null ? EMPTY_STRING_ARRAY : request.getParameterValues(name);
+            FormField field = new FormField(name);
+            for (String value : values) {
+                field.addValue(value);
+            }
+            formMap.put(name, field);
         }
     }
-    
-    public FormMap(Map<String, String> paramMap) {
+
+    public FormMap(Map<String, FormField> paramMap) {
         formMap.putAll(paramMap);
     }
-    
-    public void addFormField(String fieldName, String fieldValue) {
-        formMap.put(fieldName, fieldValue);
+
+    public void addFormField(String fieldName, FormField field) {
+        formMap.put(fieldName, field);
     }
-    
+
     public void addMessage(String name, String value) {
-        formMap.put(getMessageKeyPrefix()+name, value);
+        FormField field = formMap.get(name);
+        if (field == null) {
+            field = new FormField();
+            formMap.put(name, field);
+        }
+        field.addMessage(value);
+
     }
-    
+
     public void setPrevious(String uuid) {
         this.predecessorUUID = uuid;
     }
-    
-    public String getField(String name) {
+
+    public FormField getField(String name) {
         return formMap.get(name);
     }
-    
-    public Map<String,String> getValue(){
+
+    public Map<String, FormField> getValue() {
         return formMap;
     }
-    
-    public Map<String,String> getMessage(){
-        if(messages != null) {
-            return messages;
+
+    /**
+     * For each of th fields we only fetch it's first error message
+     * (if field contains error message(s)).
+     *
+     * @return map containing (single) field  (error) messages
+     * @see FormField#getMessages()
+     */
+    public Map<String, String> getMessage() {
+        if (message != null) {
+            return message;
         } else {
-            messages = new HashMap<String,String>();
-            for(Entry<String,String> entry : formMap.entrySet()) {
-                if(entry.getKey().startsWith(getMessageKeyPrefix())) {
-                    messages.put(entry.getKey().substring(getMessageKeyPrefix().length()), entry.getValue());
+            message = new HashMap<String, String>();
+            for (FormField field : formMap.values()) {
+                List<String> fieldMessages = field.getMessages();
+                if (fieldMessages.size() > 0) {
+                    message.put(field.getName(), fieldMessages.get(0));
                 }
             }
         }
+        return message;
+    }
+
+    /**
+     * Returns all field (error) messages.
+     * If you only need to display one message, per form field please see {@link #getMessage()}
+     *
+     * @return empty map or map of error messages
+     */
+    public Map<String, List<String>> getMessages() {
+        if (messages != null) {
+            return messages;
+        } else {
+            messages = new HashMap<String, List<String>>();
+            for (FormField field : formMap.values()) {
+                messages.put(field.getName(), field.getMessages());
+            }
+        }
         return messages;
     }
-    
-    public String getPrevious(){
+
+    public String getPrevious() {
         return this.predecessorUUID;
     }
-    
-    public Map<String,String> getFormMap(){
+
+    public Map<String, FormField> getFormMap() {
         return formMap;
     }
-    
+
+    public boolean isSealed() {
+        return sealed;
+    }
+
+    /**
+     * Seal this form data. Once sealed, data will not be read anymore when user reloads the page
+     *
+     * @param sealed boolean, if true, we'll finalize our form
+     */
+    public void setSealed(final boolean sealed) {
+        this.sealed = sealed;
+    }
+
     /**
      * Override this method if you need a different prefix
-     * @return the message prefix. 
+     *
+     * @return the message prefix.
      */
-    public String getMessageKeyPrefix(){
-        return MESSAGE_PREFIX;
+    @Deprecated
+    public String getMessageKeyPrefix() {
+        return "msg-key::";
     }
+
+
 }
 
diff --git a/client/src/test/java/org/hippoecm/hst/component/support/forms/FormFieldTest.java b/client/src/test/java/org/hippoecm/hst/component/support/forms/FormFieldTest.java
new file mode 100644
index 0000000..feb0886
--- /dev/null
+++ b/client/src/test/java/org/hippoecm/hst/component/support/forms/FormFieldTest.java
@@ -0,0 +1,24 @@
+package org.hippoecm.hst.component.support.forms;
+
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import static junit.framework.Assert.*;
+
+/**
+ * @version $Id$
+ */
+public class FormFieldTest {
+    @SuppressWarnings({"UnusedDeclaration"})
+    private static Logger log = LoggerFactory.getLogger(FormFieldTest.class);
+
+
+    @Test
+    public void testHashCode() throws Exception {
+        FormField field1 = new FormField("field1");
+        field1.addValue("value1");
+        FormField field2 = new FormField("field1");
+        field2.addValue("XXX");
+        assertTrue("Expected fields to be same, because they have same name", field1.equals(field2));
+    }
+}
diff --git a/demosuite/content/src/main/resources/hstconfiguration/democommon/components.xml b/demosuite/content/src/main/resources/hstconfiguration/democommon/components.xml
index 9d8fe1b..333f5e2 100644
--- a/demosuite/content/src/main/resources/hstconfiguration/democommon/components.xml
+++ b/demosuite/content/src/main/resources/hstconfiguration/democommon/components.xml
@@ -345,6 +345,25 @@
 			</sv:property>
 		</sv:node>
 	</sv:node>
+  <sv:node sv:name="bodyformpage">
+    <sv:property sv:name="jcr:primaryType" sv:type="Name">
+      <sv:value>hst:component</sv:value>
+    </sv:property>
+    <sv:property sv:name="hst:referencecomponent" sv:type="String">
+      <sv:value>democommon/hst:components/general_threecolumnlayout</sv:value>
+    </sv:property>
+    <sv:node sv:name="content">
+      <sv:property sv:name="jcr:primaryType" sv:type="Name">
+        <sv:value>hst:component</sv:value>
+      </sv:property>
+      <sv:property sv:name="hst:componentclassname" sv:type="String">
+        <sv:value>org.hippoecm.hst.demo.components.HstForm</sv:value>
+      </sv:property>
+      <sv:property sv:name="hst:template" sv:type="String">
+        <sv:value>formpage</sv:value>
+      </sv:property>
+    </sv:node>
+  </sv:node>
 	<sv:node sv:name="bodyerror">
 		<sv:property sv:name="jcr:primaryType" sv:type="Name">
 			<sv:value>hst:component</sv:value>
diff --git a/demosuite/content/src/main/resources/hstconfiguration/democommon/pages.xml b/demosuite/content/src/main/resources/hstconfiguration/democommon/pages.xml
index ff5c11d..2bae975 100644
--- a/demosuite/content/src/main/resources/hstconfiguration/democommon/pages.xml
+++ b/demosuite/content/src/main/resources/hstconfiguration/democommon/pages.xml
@@ -185,22 +185,38 @@
             </sv:property>
         </sv:node>
     </sv:node>
-  
-    <sv:node sv:name="productpage">
-		<sv:property sv:name="jcr:primaryType" sv:type="Name">
-			<sv:value>hst:component</sv:value>
-		</sv:property>
-		<sv:property sv:name="hst:referencecomponent" sv:type="String">
-			<sv:value>democommon/hst:pages/standard</sv:value>
-		</sv:property>
-		<sv:node sv:name="body">
-			<sv:property sv:name="jcr:primaryType" sv:type="Name">
-				<sv:value>hst:component</sv:value>
-			</sv:property>
-			<sv:property sv:name="hst:referencecomponent" sv:type="String">
-				<sv:value>democommon/hst:components/bodyproductpage</sv:value>
-			</sv:property>
-		</sv:node>
-	</sv:node>
+
+  <sv:node sv:name="productpage">
+    <sv:property sv:name="jcr:primaryType" sv:type="Name">
+      <sv:value>hst:component</sv:value>
+    </sv:property>
+    <sv:property sv:name="hst:referencecomponent" sv:type="String">
+      <sv:value>democommon/hst:pages/standard</sv:value>
+    </sv:property>
+    <sv:node sv:name="body">
+      <sv:property sv:name="jcr:primaryType" sv:type="Name">
+        <sv:value>hst:component</sv:value>
+      </sv:property>
+      <sv:property sv:name="hst:referencecomponent" sv:type="String">
+        <sv:value>democommon/hst:components/bodyproductpage</sv:value>
+      </sv:property>
+    </sv:node>
+  </sv:node>
+  <sv:node sv:name="formpage">
+    <sv:property sv:name="jcr:primaryType" sv:type="Name">
+      <sv:value>hst:component</sv:value>
+    </sv:property>
+    <sv:property sv:name="hst:referencecomponent" sv:type="String">
+      <sv:value>democommon/hst:pages/standard</sv:value>
+    </sv:property>
+    <sv:node sv:name="body">
+      <sv:property sv:name="jcr:primaryType" sv:type="Name">
+        <sv:value>hst:component</sv:value>
+      </sv:property>
+      <sv:property sv:name="hst:referencecomponent" sv:type="String">
+        <sv:value>democommon/hst:components/bodyformpage</sv:value>
+      </sv:property>
+    </sv:node>
+  </sv:node>
 </sv:node>
 
diff --git a/demosuite/content/src/main/resources/hstconfiguration/democommon/templates.xml b/demosuite/content/src/main/resources/hstconfiguration/democommon/templates.xml
index f630767..c20750f 100644
--- a/demosuite/content/src/main/resources/hstconfiguration/democommon/templates.xml
+++ b/demosuite/content/src/main/resources/hstconfiguration/democommon/templates.xml
@@ -172,6 +172,14 @@
 			<sv:value>jsp/components/main/productpage.jsp</sv:value>
 		</sv:property>
 	</sv:node>
+  <sv:node sv:name="formpage">
+		<sv:property sv:name="jcr:primaryType" sv:type="Name">
+			<sv:value>hst:template</sv:value>
+		</sv:property>
+		<sv:property sv:name="hst:renderpath" sv:type="String">
+			<sv:value>jsp/components/main/formpage.jsp</sv:value>
+		</sv:property>
+	</sv:node>
 	<sv:node sv:name="table">
 		<sv:property sv:name="jcr:primaryType" sv:type="Name">
 			<sv:value>hst:template</sv:value>
diff --git a/demosuite/content/src/main/resources/hstconfiguration/demosite/sitemap.xml b/demosuite/content/src/main/resources/hstconfiguration/demosite/sitemap.xml
index 8627765..b415ca0 100644
--- a/demosuite/content/src/main/resources/hstconfiguration/demosite/sitemap.xml
+++ b/demosuite/content/src/main/resources/hstconfiguration/demosite/sitemap.xml
@@ -304,8 +304,16 @@
             </sv:property>
         </sv:node>
     </sv:node>
-    
-    <!-- Example how to use sitemap item handlers -->
+  <sv:node sv:name="form">
+    <sv:property sv:name="jcr:primaryType" sv:type="Name">
+      <sv:value>hst:sitemapitem</sv:value>
+    </sv:property>
+    <sv:property sv:name="hst:componentconfigurationid" sv:type="String">
+      <sv:value>democommon/hst:pages/formpage</sv:value>
+    </sv:property>
+  </sv:node>
+
+  <!-- Example how to use sitemap item handlers -->
     <sv:node sv:name="cached">
         <sv:property sv:name="jcr:primaryType" sv:type="Name">
             <sv:value>hst:sitemapitem</sv:value>
diff --git a/demosuite/site/src/main/java/org/hippoecm/hst/demo/components/HstForm.java b/demosuite/site/src/main/java/org/hippoecm/hst/demo/components/HstForm.java
new file mode 100644
index 0000000..0a72dfc
--- /dev/null
+++ b/demosuite/site/src/main/java/org/hippoecm/hst/demo/components/HstForm.java
@@ -0,0 +1,45 @@
+package org.hippoecm.hst.demo.components;
+
+import org.hippoecm.hst.component.support.forms.BaseFormHstComponent;
+import org.hippoecm.hst.component.support.forms.FormField;
+import org.hippoecm.hst.component.support.forms.FormMap;
+import org.hippoecm.hst.core.component.HstComponentException;
+import org.hippoecm.hst.core.component.HstRequest;
+import org.hippoecm.hst.core.component.HstResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @version $Id$
+ */
+public class HstForm extends BaseFormHstComponent {
+    @SuppressWarnings({"UnusedDeclaration"})
+    private static Logger log = LoggerFactory.getLogger(HstForm.class);
+    private static final String[] DEFINED_FORM_FIELDS = {"name", "email", "textarea", "checkbox"};
+
+    @Override
+    public void doAction(final HstRequest request, final HstResponse response) throws HstComponentException {
+        super.doAction(request, response);
+
+        super.doAction(request, response);
+        FormMap formMap = new FormMap(request, DEFINED_FORM_FIELDS);
+        FormField checkbox = formMap.getField("checkbox");
+        if(checkbox ==null || checkbox.getValues().size()==0){
+            formMap.addMessage("checkbox", "Please choose a color");
+            formMap.addMessage("checkbox", "Please choose any color");
+        }
+        if(request.getParameterValues("seal") !=null){
+            log.info("sealing data");
+            formMap.setSealed(true);
+        }
+        super.persistFormMap(request, response, formMap, null);
+    }
+
+    @Override
+    public void doBeforeRender(final HstRequest request, final HstResponse response) throws HstComponentException {
+        super.doBeforeRender(request, response);
+        FormMap formMap = new FormMap();
+        super.populate(request, formMap);
+        request.setAttribute("form", formMap);
+    }
+}
diff --git a/demosuite/site/src/main/webapp/jsp/components/main/formpage.jsp b/demosuite/site/src/main/webapp/jsp/components/main/formpage.jsp
new file mode 100644
index 0000000..c16ae47
--- /dev/null
+++ b/demosuite/site/src/main/webapp/jsp/components/main/formpage.jsp
@@ -0,0 +1,115 @@
+<%--
+  Copyright 2008-2009 Hippo
+
+  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. --%>
+
+<%@ page language="java" %>
+<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
+<%@ taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x" %>
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib uri="http://www.hippoecm.org/jsp/hst/core" prefix='hst' %>
+
+<hst:defineObjects/>
+
+<hst:head-contribution keyHint="title"><title>${document.title}</title></hst:head-contribution>
+<hst:element name="script" var="yui3Elem">
+    <hst:attribute name="type" value="text/javascript"/>
+    <hst:attribute name="src" value="http://yui.yahooapis.com/3.2.0/build/yui/yui-min.js"/>
+</hst:element>
+<hst:head-contribution keyHint="yui3" element="${yui3Elem}"/>
+
+<style type="text/css">
+    .form_error {
+        color: red;
+    }
+</style>
+<div id="<hst:namespace/>detailPane" class="yui-u">
+    <h1>From example</h1>
+    <%--@elvariable id="form" type="org.hippoecm.hst.component.support.forms.FormMap"--%>
+    <form name="hstForm" action="<hst:actionURL />" method="post">
+
+        <input type="hidden" name="previous" value="${form.previous}"/>
+        <br/>
+        <table>
+            <tr>
+
+                <td>Name</td>
+                <td><input type="text" name="name" value="${form.value['name'].value}"/></td>
+                <td class="form_error">${form.message['name']}</td>
+            </tr>
+            <tr>
+                <td>Email</td>
+                <td><input type="text" name="email" value="${form.value['email'].value}"/></td>
+                <td class="form_error">${form.message['email']}</td>
+            </tr>
+            <tr>
+                <td>Text</td>
+                <td><textarea name="textarea" rows="4" cols="40">${form.value['textarea'].value}</textarea></td>
+                <td class="form_error">${form.message['textarea']}</td>
+            </tr>
+            <tr>
+                <td>Checkbox</td>
+                <td>
+                    <p>
+                        <input type="checkbox" name="checkbox"  ${empty form.value['checkbox'].values["Red"] ? '':'checked="checked"'} value="Red"> Red
+                    </p>
+
+                    <p>
+                        <input type="checkbox" name="checkbox"  ${empty form.value['checkbox'].values["White"] ? '':'checked="checked"'} value="White"> White
+                    </p>
+
+                    <p>
+                        <input type="checkbox" name="checkbox"  ${empty form.value['checkbox'].values["Blue"] ? '':'checked="checked"'} value="Blue"> Blue
+                    </p>
+
+                </td>
+                <td class="form_error">
+                </td>
+            </tr>
+            <tr>
+                <td>Seal form:</td>
+                <td>
+                    <p>
+                        <input type="checkbox" name="seal"  value="true"> Seal form data
+                    </p>
+                </td>
+                <td class="form_error">
+                </td>
+            </tr>
+            <tr>
+                <td class="form_error">
+                    <c:if test="${not empty form.message['checkbox']}">
+                        <p>First message: </p> ${form.message['checkbox']}
+                        <p>All messages:</p>
+                        <c:forEach var="msg" items="${form.messages['checkbox']}" varStatus="index">
+                            <p>${index.index} : ${msg}</p>
+                        </c:forEach>
+                    </c:if>
+                </td>
+                <td>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <c:if test="${form.previous != null}">
+                        <input type="submit" name="prev" value="prev"/>
+                    </c:if>
+                </td>
+                <td><input type="submit" value="send"/></td>
+            </tr>
+        </table>
+    </form>
+
+    </form>
+</div>
diff --git a/testsuite/site/src/main/java/org/hippoecm/hst/components/Contact.java b/testsuite/site/src/main/java/org/hippoecm/hst/components/Contact.java
index 4d34f0a..06c0483 100644
--- a/testsuite/site/src/main/java/org/hippoecm/hst/components/Contact.java
+++ b/testsuite/site/src/main/java/org/hippoecm/hst/components/Contact.java
@@ -47,7 +47,7 @@ public class Contact extends BaseFormHstComponent {
             return;
         }
         // Do a really simple validation: 
-        if(formMap.getField("email") != null && formMap.getField("email").contains("@")) {
+        if(formMap.getField("email") != null && formMap.getField("email").getValue().contains("@")) {
             // success
             
             // do your business logic
diff --git a/testsuite/site/src/main/java/org/hippoecm/hst/components/ContactSpring.java b/testsuite/site/src/main/java/org/hippoecm/hst/components/ContactSpring.java
index 046a8b5..3c2ff26 100644
--- a/testsuite/site/src/main/java/org/hippoecm/hst/components/ContactSpring.java
+++ b/testsuite/site/src/main/java/org/hippoecm/hst/components/ContactSpring.java
@@ -60,12 +60,12 @@ public class ContactSpring extends BaseFormHstComponent {
             return;
         }
         // Do a really simple validation: 
-        if(formMap.getField("email") != null && formMap.getField("email").contains("@")) {
+        if(formMap.getField("email") != null && formMap.getField("email").getValue().contains("@")) {
             // success
             // send email here.
             SimpleMailMessage msg = new SimpleMailMessage(this.templateMessage);
-            msg.setFrom(formMap.getField("email"));
-            msg.setText(formMap.getField("textarea"));
+            msg.setFrom(formMap.getField("email").getValue());
+            msg.setText(formMap.getField("textarea").getValue());
             
             try {
                 this.mailSender.send(msg);
diff --git a/testsuite/site/src/main/webapp/jsp/components/contactdispatch.jsp b/testsuite/site/src/main/webapp/jsp/components/contactdispatch.jsp
index aa50269..4ec4b61 100644
--- a/testsuite/site/src/main/webapp/jsp/components/contactdispatch.jsp
+++ b/testsuite/site/src/main/webapp/jsp/components/contactdispatch.jsp
@@ -132,18 +132,19 @@ if (HstRequest.ACTION_PHASE.equals(hstRequestLifecyclePhase)) {
     <br/>
     <table>
         <tr>
+
             <td>Name</td>
-            <td><input type="text" name="name" value="${form.value['name']}" /></td>
+            <td><input type="text" name="name" value="${form.value['name'].value}" /></td>
             <td><font style="color:red">${form.message['name']}</font></td>
         </tr>
         <tr>
             <td>Email</td>
-            <td><input type="text" name="email" value="${form.value['email']}"/></td>
+            <td><input type="text" name="email" value="${form.value['email'].value}"/></td>
             <td><font style="color:red">${form.message['email']}</font></td>
         </tr>
         <tr>
             <td>Text</td>
-            <td><textarea name="textarea">${form.value['textarea']}</textarea></td>
+            <td><textarea name="textarea">${form.value['textarea'].value}</textarea></td>
             <td><font style="color:red">${form.message['textarea']}</font></td>
         </tr>
         <tr>
diff --git a/toolkit-resources/addon/toolkit-cnd/cnd/src/main/resources/hst-types.cnd b/toolkit-resources/addon/toolkit-cnd/cnd/src/main/resources/hst-types.cnd
index 0ac447a..81d3721 100644
--- a/toolkit-resources/addon/toolkit-cnd/cnd/src/main/resources/hst-types.cnd
+++ b/toolkit-resources/addon/toolkit-cnd/cnd/src/main/resources/hst-types.cnd
@@ -1,247 +1,254 @@
-/*
- * Copyright 2008 Hippo
- *
- * 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.
- */
-
-<nt='http://www.jcp.org/jcr/nt/1.0'>
-<mix='http://www.jcp.org/jcr/mix/1.0'>
-<hst='http://www.hippoecm.org/hst/nt/2.0'>
-<hippo='http://www.onehippo.org/jcr/hippo/nt/2.0'>
-
-[hst:descriptive] mixin
-- hst:description (string)
-+ hst:icon (hippo:resource) = hippo:resource
-
-[hst:template] > nt:base, mix:referenceable, hst:descriptive
-- hst:renderpath (string)
-- hst:containers (string) multiple
-
-[hst:templates] > nt:base, mix:referenceable orderable
-- hst:inheritsfrom (string) 
-+ * (hst:template)
-
-[hst:abstractcomponent] > nt:base, mix:referenceable, hst:descriptive orderable 
-- hst:template (string)
-- hst:componentclassname (string)
-- hst:serveresourcepath (string)
-- hst:referencename (string)
-- hst:page_errorhandlerclassname (string)
-- hst:parameternames (string) multiple
-- hst:parametervalues (string) multiple
-- hst:inheritsfrom (string)
-
-[hst:component] > hst:abstractcomponent orderable
-- hst:referencecomponent(string)
-+ * (hst:abstractcomponent)
-
-[hst:containeritemcomponent] > hst:abstractcomponent orderable
-- hst:referencecomponent(string)
-- hst:samplecontent (string)
-+ * (hst:abstractcomponent)
-
-[hst:containercomponent] > hst:abstractcomponent orderable
-- hst:containertype (string) mandatory
-+ * (hst:containeritemcomponent)
-
-[hst:components] > nt:base, mix:referenceable orderable
-- hst:inheritsfrom (string)
-+ * (hst:abstractcomponent)
-
-
-[hst:pages] > nt:base, mix:referenceable orderable
-- hst:inheritsfrom (string)
-+ * (hst:abstractcomponent)
-
-[hst:sitemapitemhandler] > nt:base, mix:referenceable
-- hst:sitemapitemhandlerclassname (string) mandatory
-- * (string) 
-- * (string) multiple
-- * (boolean) 
-- * (boolean) multiple
-- * (long) 
-- * (long) multiple
-- * (double) 
-- * (double) multiple
-- * (date) 
-- * (date) multiple
-
-[hst:sitemapitemhandlers] >  nt:base, mix:referenceable orderable
-- hst:inheritsfrom (string)
-+ * (hst:sitemapitemhandler)
-
-[hst:script] > nt:base, mix:referenceable
-- hst:template (string) mandatory
-- hst:language (string) mandatory
-- * (string)
-
-[hst:scripts] > nt:base, mix:referenceable orderable
-- hst:inheritsfrom (string)
-+ * (hst:script)
-
-[hst:sitemapitem] > nt:base, mix:referenceable
-- hst:value
-- hst:relativecontentpath (string)
-- hst:componentconfigurationid (string)
-- hst:portletcomponentconfigurationid (string)
-- hst:sitemapitemhandlerids (string) multiple
-- hst:parameternames (string) multiple
-- hst:parametervalues (string) multiple
-- hst:namedpipeline (string)
-- hst:statuscode (long)
-- hst:errorcode (long)
-- hst:excludedforlinkrewriting (boolean)
-- hst:secured (boolean)
-- hst:roles (string) multiple
-- hst:users (string) multiple
-- hst:locale (string)
-- hst:inheritsfrom (string)
-+ * (hst:sitemapitem)
-
-[hst:sitemap] > nt:base, mix:referenceable orderable
-- hst:inheritsfrom (string)
-+ * (hst:sitemapitem)
-
-[hst:sitemenuitem] > nt:base, mix:referenceable orderable
-- hst:referencesitemapitem (string)
-- hst:externallink (string)
-- hst:foldersonly (boolean)
-- hst:repobased (boolean)
-- hst:depth (long)
-- hst:parameternames (string) multiple
-- hst:parametervalues (string) multiple
-- hst:inheritsfrom (string)
-+ * (hst:sitemenuitem)
-
-[hst:sitemenu] > nt:base, mix:referenceable orderable
-- hst:inheritsfrom (string)
-+ * (hst:sitemenuitem)
-
-[hst:sitemenus] > nt:base, mix:referenceable
-- hst:inheritsfrom (string)
-+ * (hst:sitemenu)
-
-
-[hst:configuration] > nt:base, mix:referenceable, mix:versionable
-- hst:inheritsfrom (string)
-+ hst:components (hst:components) = hst:components
-+ hst:pages (hst:pages) = hst:pages
-+ hst:sitemap (hst:sitemap) = hst:sitemap
-+ hst:templates (hst:templates) = hst:templates
-+ hst:sitemenus (hst:sitemenus) = hst:sitemenus
-+ hst:sitemapitemhandlers (hst:sitemapitemhandlers) = hst:sitemapitemhandlers
-+ hst:scripts (hst:scripts) = hst:scripts
-
-[hst:configurations] > nt:base, mix:referenceable, mix:versionable
-+ * (hst:configuration) = hst:configuration version
-
-[hst:site] > nt:base, mix:referenceable, mix:versionable
-+ hst:content (hippo:facetselect) mandatory
-- hst:configurationpath (string) mandatory
-- hst:portalconfigurationenabled (boolean)
-
-[hst:sites] > nt:base, mix:referenceable, mix:versionable
-+ * (hst:site) = hst:site version
-
-[hst:primarydomain] mixin
-
-[hst:redirectdomain] mixin 
-- hst:redirect (string)
-
-
-[hst:sitemount] > nt:base, mix:referenceable
-+ * (hst:sitemount)
-// the path to the (sub)site. for example /preview/myproject
-- hst:mountpoint (string)
-// whether this sitemount is associated with a hstSite
-- hst:issitemount (boolean)
-// if you do not want to sitemount to run with the default hst pipeline, you can configure the pipeline to be used here
-- hst:namedpipeline (string)
-- hst:showcontextpath (boolean)
-// when this sitemount is only applicable for certain contextpath, add this property for the contextpath that is allowed (must start with '/' and no further '/' is allowed)
-- hst:onlyforcontextpath (string)
-// for embedded delegation of sites a sitemountpath needs to point to the delegated sitemount. This is only relevant for portal environment
-- hst:embeddedsitemountpath
-- hst:scheme (string)
-// whether the port should be visible in the created urls
-- hst:showport (boolean)
-- hst:parameternames (string) multiple
-- hst:parametervalues (string) multiple
-- hst:homepage (string)
-- hst:pagenotfound (string)
-- hst:locale (string)
-- hst:versioninpreviewheader (boolean)
-- hst:secured (boolean)
-- hst:roles (string) multiple
-- hst:users (string) multiple
-// the alias of this sitemount. If not present, the alias is the current name
-- hst:alias (string)
-// the primary type of this SiteMount, for example 'preview'. Default when missing is 'live'
-- hst:type (string)
-// the types this SiteMount also belongs to, for example 'composer'.
-- hst:types (string) multiple
-- hst:sessionstateful (boolean)
-// to hold all the 'free defined properties'
-- * (string)
-
-[hst:portmount] > nt:base, mix:referenceable
-+ hst:root (hst:sitemount)
-
-[hst:virtualhost] > nt:base, mix:referenceable
-+ * (hst:virtualhost)
-+ * (hst:portmount)
-+ hst:root (hst:sitemount)
-- hst:showcontextpath (boolean)
-- hst:scheme (string)
-- hst:parameternames (string) multiple
-- hst:parametervalues (string) multiple
-- hst:homepage (string)
-- hst:pagenotfound (string)
-- hst:locale (string)
-- hst:versioninpreviewheader (boolean)
-
-[hst:virtualhostgroup] > nt:base, mix:referenceable
-+ * (hst:virtualhost)
-
-[hst:virtualhosts] > nt:base, mix:referenceable, mix:versionable
-- hst:showcontextpath (boolean)
-- hst:prefixexclusions (string) multiple
-- hst:suffixexclusions (string) multiple
-- hst:scheme (string)
-- hst:defaulthostname (string)
-- hst:parameternames (string) multiple
-- hst:parametervalues (string) multiple
-- hst:homepage (string)
-- hst:pagenotfound (string)
-- hst:locale (string)
-- hst:versioninpreviewheader (boolean)
-+ * (hst:virtualhostgroup)
-
-[hst:hst] > nt:base, mix:referenceable, mix:versionable
-+ hst:configurations (hst:configurations) = hst:configurations version
-+ hst:hosts (hst:virtualhosts) = hst:virtualhosts version
-+ * (hst:sites) = hst:sites version
-
-// the hst nodetype for the contentmount's: this nodetype is for content sharing between subsites
-[hst:contentshare] > hippo:mirror
-
-[hst:formdata]
-- hst:creationtime (date) mandatory
-- hst:parameternames (string) multiple
-- hst:parametervalues (string) multiple
-- hst:predecessor (string)
-
-
-[hst:formdatacontainer] 
-+ * (hst:formdatacontainer)
+/*
+ * Copyright 2008 Hippo
+ *
+ * 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.
+ */
+
+<nt='http://www.jcp.org/jcr/nt/1.0'>
+<mix='http://www.jcp.org/jcr/mix/1.0'>
+<hst='http://www.hippoecm.org/hst/nt/2.0'>
+<hippo='http://www.onehippo.org/jcr/hippo/nt/2.0'>
+
+[hst:descriptive] mixin
+- hst:description (string)
++ hst:icon (hippo:resource) = hippo:resource
+
+[hst:template] > nt:base, mix:referenceable, hst:descriptive
+- hst:renderpath (string)
+- hst:containers (string) multiple
+
+[hst:templates] > nt:base, mix:referenceable orderable
+- hst:inheritsfrom (string) 
++ * (hst:template)
+
+[hst:abstractcomponent] > nt:base, mix:referenceable, hst:descriptive orderable 
+- hst:template (string)
+- hst:componentclassname (string)
+- hst:serveresourcepath (string)
+- hst:referencename (string)
+- hst:page_errorhandlerclassname (string)
+- hst:parameternames (string) multiple
+- hst:parametervalues (string) multiple
+- hst:inheritsfrom (string)
+
+[hst:component] > hst:abstractcomponent orderable
+- hst:referencecomponent(string)
++ * (hst:abstractcomponent)
+
+[hst:containeritemcomponent] > hst:abstractcomponent orderable
+- hst:referencecomponent(string)
+- hst:samplecontent (string)
++ * (hst:abstractcomponent)
+
+[hst:containercomponent] > hst:abstractcomponent orderable
+- hst:containertype (string) mandatory
++ * (hst:containeritemcomponent)
+
+[hst:components] > nt:base, mix:referenceable orderable
+- hst:inheritsfrom (string)
++ * (hst:abstractcomponent)
+
+
+[hst:pages] > nt:base, mix:referenceable orderable
+- hst:inheritsfrom (string)
++ * (hst:abstractcomponent)
+
+[hst:sitemapitemhandler] > nt:base, mix:referenceable
+- hst:sitemapitemhandlerclassname (string) mandatory
+- * (string) 
+- * (string) multiple
+- * (boolean) 
+- * (boolean) multiple
+- * (long) 
+- * (long) multiple
+- * (double) 
+- * (double) multiple
+- * (date) 
+- * (date) multiple
+
+[hst:sitemapitemhandlers] >  nt:base, mix:referenceable orderable
+- hst:inheritsfrom (string)
++ * (hst:sitemapitemhandler)
+
+[hst:script] > nt:base, mix:referenceable
+- hst:template (string) mandatory
+- hst:language (string) mandatory
+- * (string)
+
+[hst:scripts] > nt:base, mix:referenceable orderable
+- hst:inheritsfrom (string)
++ * (hst:script)
+
+[hst:sitemapitem] > nt:base, mix:referenceable
+- hst:value
+- hst:relativecontentpath (string)
+- hst:componentconfigurationid (string)
+- hst:portletcomponentconfigurationid (string)
+- hst:sitemapitemhandlerids (string) multiple
+- hst:parameternames (string) multiple
+- hst:parametervalues (string) multiple
+- hst:namedpipeline (string)
+- hst:statuscode (long)
+- hst:errorcode (long)
+- hst:excludedforlinkrewriting (boolean)
+- hst:secured (boolean)
+- hst:roles (string) multiple
+- hst:users (string) multiple
+- hst:locale (string)
+- hst:inheritsfrom (string)
++ * (hst:sitemapitem)
+
+[hst:sitemap] > nt:base, mix:referenceable orderable
+- hst:inheritsfrom (string)
++ * (hst:sitemapitem)
+
+[hst:sitemenuitem] > nt:base, mix:referenceable orderable
+- hst:referencesitemapitem (string)
+- hst:externallink (string)
+- hst:foldersonly (boolean)
+- hst:repobased (boolean)
+- hst:depth (long)
+- hst:parameternames (string) multiple
+- hst:parametervalues (string) multiple
+- hst:inheritsfrom (string)
++ * (hst:sitemenuitem)
+
+[hst:sitemenu] > nt:base, mix:referenceable orderable
+- hst:inheritsfrom (string)
++ * (hst:sitemenuitem)
+
+[hst:sitemenus] > nt:base, mix:referenceable
+- hst:inheritsfrom (string)
++ * (hst:sitemenu)
+
+
+[hst:configuration] > nt:base, mix:referenceable, mix:versionable
+- hst:inheritsfrom (string)
++ hst:components (hst:components) = hst:components
++ hst:pages (hst:pages) = hst:pages
++ hst:sitemap (hst:sitemap) = hst:sitemap
++ hst:templates (hst:templates) = hst:templates
++ hst:sitemenus (hst:sitemenus) = hst:sitemenus
++ hst:sitemapitemhandlers (hst:sitemapitemhandlers) = hst:sitemapitemhandlers
++ hst:scripts (hst:scripts) = hst:scripts
+
+[hst:configurations] > nt:base, mix:referenceable, mix:versionable
++ * (hst:configuration) = hst:configuration version
+
+[hst:site] > nt:base, mix:referenceable, mix:versionable
++ hst:content (hippo:facetselect) mandatory
+- hst:configurationpath (string) mandatory
+- hst:portalconfigurationenabled (boolean)
+
+[hst:sites] > nt:base, mix:referenceable, mix:versionable
++ * (hst:site) = hst:site version
+
+[hst:primarydomain] mixin
+
+[hst:redirectdomain] mixin 
+- hst:redirect (string)
+
+
+[hst:sitemount] > nt:base, mix:referenceable
++ * (hst:sitemount)
+// the path to the (sub)site. for example /preview/myproject
+- hst:mountpoint (string)
+// whether this sitemount is associated with a hstSite
+- hst:issitemount (boolean)
+// if you do not want to sitemount to run with the default hst pipeline, you can configure the pipeline to be used here
+- hst:namedpipeline (string)
+- hst:showcontextpath (boolean)
+// when this sitemount is only applicable for certain contextpath, add this property for the contextpath that is allowed (must start with '/' and no further '/' is allowed)
+- hst:onlyforcontextpath (string)
+// for embedded delegation of sites a sitemountpath needs to point to the delegated sitemount. This is only relevant for portal environment
+- hst:embeddedsitemountpath
+- hst:scheme (string)
+// whether the port should be visible in the created urls
+- hst:showport (boolean)
+- hst:parameternames (string) multiple
+- hst:parametervalues (string) multiple
+- hst:homepage (string)
+- hst:pagenotfound (string)
+- hst:locale (string)
+- hst:versioninpreviewheader (boolean)
+- hst:secured (boolean)
+- hst:roles (string) multiple
+- hst:users (string) multiple
+// the alias of this sitemount. If not present, the alias is the current name
+- hst:alias (string)
+// the primary type of this SiteMount, for example 'preview'. Default when missing is 'live'
+- hst:type (string)
+// the types this SiteMount also belongs to, for example 'composer'.
+- hst:types (string) multiple
+- hst:sessionstateful (boolean)
+// to hold all the 'free defined properties'
+- * (string)
+
+[hst:portmount] > nt:base, mix:referenceable
++ hst:root (hst:sitemount)
+
+[hst:virtualhost] > nt:base, mix:referenceable
++ * (hst:virtualhost)
++ * (hst:portmount)
++ hst:root (hst:sitemount)
+- hst:showcontextpath (boolean)
+- hst:scheme (string)
+- hst:parameternames (string) multiple
+- hst:parametervalues (string) multiple
+- hst:homepage (string)
+- hst:pagenotfound (string)
+- hst:locale (string)
+- hst:versioninpreviewheader (boolean)
+
+[hst:virtualhostgroup] > nt:base, mix:referenceable
++ * (hst:virtualhost)
+
+[hst:virtualhosts] > nt:base, mix:referenceable, mix:versionable
+- hst:showcontextpath (boolean)
+- hst:prefixexclusions (string) multiple
+- hst:suffixexclusions (string) multiple
+- hst:scheme (string)
+- hst:defaulthostname (string)
+- hst:parameternames (string) multiple
+- hst:parametervalues (string) multiple
+- hst:homepage (string)
+- hst:pagenotfound (string)
+- hst:locale (string)
+- hst:versioninpreviewheader (boolean)
++ * (hst:virtualhostgroup)
+
+[hst:hst] > nt:base, mix:referenceable, mix:versionable
++ hst:configurations (hst:configurations) = hst:configurations version
++ hst:hosts (hst:virtualhosts) = hst:virtualhosts version
++ * (hst:sites) = hst:sites version
+
+// the hst nodetype for the contentmount's: this nodetype is for content sharing between subsites
+[hst:contentshare] > hippo:mirror
+
+
+[hst:formfieldvalue]
+- hst:formfieldname (string) mandatory
+- hst:formfielddata (string) multiple
+- hst:formfieldmessages (string) multiple
+
+
+[hst:formdata]
+- hst:creationtime (date) mandatory
++ hst:formfieldvalue (hst:formfieldvalue) multiple
+- hst:predecessor (string)
+- hst:sealed (boolean)
+
+
+[hst:formdatacontainer] 
++ * (hst:formdatacontainer)
 + * (hst:formdata)
\ No newline at end of file
-- 
1.7.1

