You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by dk...@apache.org on 2019/07/25 03:00:01 UTC

[sling-org-apache-sling-app-cms] branch dklco/form-framework created (now 2a8051a)

This is an automated email from the ASF dual-hosted git repository.

dklco pushed a change to branch dklco/form-framework
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-app-cms.git.


      at 2a8051a  Adding initial commit of the form framework

This branch includes the following new commits:

     new 2a8051a  Adding initial commit of the form framework

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[sling-org-apache-sling-app-cms] 01/01: Adding initial commit of the form framework

Posted by dk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch dklco/form-framework
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-app-cms.git

commit 2a8051a62f03d7037f2880adf1dad6ea7b94fd07
Author: Dan Klco <dk...@apache.org>
AuthorDate: Wed Jul 24 22:59:45 2019 -0400

    Adding initial commit of the form framework
---
 .../java/org/apache/sling/cms/ResourceTree.java    |  55 ++++++
 builder/src/main/provisioning/cms.txt              |   1 +
 .../internal/listeners/FileMetadataExtractor.java  | 155 +++++++++++++++
 core/src/test/resources/simplelogger.properties    |  17 ++
 pom.xml                                            |   8 +-
 reference/pom.xml                                  |  31 ++-
 .../sling/cms/reference/forms/FieldHandler.java    |  58 ++++++
 .../sling/cms/reference/forms/FormAction.java      |  44 +++++
 .../cms/reference/forms/FormActionResult.java      |  55 ++++++
 .../sling/cms/reference/forms/FormException.java   |  35 ++++
 .../sling/cms/reference/forms/FormRequest.java     |  37 ++++
 .../cms/reference/forms/FormValueProvider.java     |  39 ++++
 .../cms/reference/forms/impl/FormHandler.java      | 105 ++++++++++
 .../cms/reference/forms/impl/FormRequestImpl.java  | 125 ++++++++++++
 .../forms/impl/actions/SendEmailAction.java        | 121 ++++++++++++
 .../forms/impl/actions/SendEmailActonConfig.java   |  40 ++++
 .../forms/impl/actions/UpdateProfileAction.java    | 101 ++++++++++
 .../forms/impl/fields/SelectionHandler.java        |  88 +++++++++
 .../forms/impl/fields/TextareaHandler.java         |  57 ++++++
 .../forms/impl/fields/TextfieldHandler.java        | 125 ++++++++++++
 .../providers/UserProfileFormValueProvider.java    | 103 ++++++++++
 .../main/resources/OSGI-INF/l10n/bundle.properties |  28 ++-
 .../components/forms/actions/sendemail.json        |   5 +
 .../components/forms/actions/sendemail/edit.json   |  37 ++++
 .../forms/actions/sendemail/sendemail.jsp          |  32 +++
 .../components/forms/actions/updateprofile.json    |   5 +
 .../forms/actions/updateprofile/edit.json          |  15 ++
 .../forms/actions/updateprofile/updateprofile.jsp  |  26 +++
 .../components/forms/fields/selection.json         |   5 +
 .../components/forms/fields/selection/edit.json    | 103 ++++++++++
 .../forms/fields/selection/selection.jsp           |  86 ++++++++
 .../components/forms/fields/textarea.json          |   5 +
 .../components/forms/fields/textarea/edit.json     |  68 +++++++
 .../components/forms/fields/textarea/textarea.jsp  |  44 +++++
 .../components/forms/fields/textfield.json         |   5 +
 .../components/forms/fields/textfield/edit.json    | 160 +++++++++++++++
 .../forms/fields/textfield/textfield.jsp           |  46 +++++
 .../apps/reference/components/forms/fieldset.json  |   5 +
 .../reference/components/forms/fieldset/edit.json  |  15 ++
 .../components/forms/fieldset/fieldset.jsp         |  23 +++
 .../apps/reference/components/forms/form.json      |   5 +
 .../reference/components/forms/form/config.json    |  81 ++++++++
 .../apps/reference/components/forms/form/edit.json |  40 ++++
 .../apps/reference/components/forms/form/form.jsp  |  47 +++++
 .../components/forms/providers/userprofile.json    |   5 +
 .../forms/providers/userprofile/edit.json          |  15 ++
 .../forms/providers/userprofile/userprofile.jsp    |  26 +++
 .../cms/reference/form/FormActionResultTest.java   |  42 ++++
 .../cms/reference/form/impl/FormHandlerTest.java   | 125 ++++++++++++
 .../reference/form/impl/FormRequestImplTest.java   |  79 ++++++++
 .../reference/form/impl/SlingContextHelper.java    |  28 +++
 .../form/impl/actions/SendEmailActionTest.java     | 128 ++++++++++++
 .../form/impl/fields/SelectionHandlerTest.java     | 114 +++++++++++
 .../form/impl/fields/TextareaHandlerTest.java      | 122 ++++++++++++
 .../form/impl/fields/TextfieldHandlerTest.java     | 220 +++++++++++++++++++++
 reference/src/test/resources/form.json             | 156 +++++++++++++++
 56 files changed, 3341 insertions(+), 5 deletions(-)

diff --git a/api/src/main/java/org/apache/sling/cms/ResourceTree.java b/api/src/main/java/org/apache/sling/cms/ResourceTree.java
new file mode 100644
index 0000000..3096b33
--- /dev/null
+++ b/api/src/main/java/org/apache/sling/cms/ResourceTree.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.cms;
+
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import org.apache.sling.api.resource.Resource;
+
+public class ResourceTree {
+
+    private Resource root;
+
+    public static Stream<ResourceTree> stream(Resource resource) {
+        return new ResourceTree(resource).streamTree();
+    }
+
+    public static Stream<ResourceTree> stream(Resource resource, String filterType) {
+        return new ResourceTree(resource).streamTree(filterType);
+    }
+
+    private ResourceTree(Resource root) {
+        this.root = root;
+    }
+
+    public Resource getResource() {
+        return root;
+    }
+
+    private Stream<ResourceTree> streamTree() {
+        return Stream.concat(Stream.of(this), StreamSupport.stream(root.getChildren().spliterator(), false)
+                .map(ResourceTree::new).flatMap(ResourceTree::streamTree));
+    }
+
+    private Stream<ResourceTree> streamTree(String filterType) {
+        return Stream.concat(Stream.of(this),
+                StreamSupport.stream(root.getChildren().spliterator(), false)
+                        .filter(c -> filterType.equals(c.getResourceType())).map(ResourceTree::new)
+                        .flatMap(rt -> rt.streamTree(filterType)));
+    }
+}
\ No newline at end of file
diff --git a/builder/src/main/provisioning/cms.txt b/builder/src/main/provisioning/cms.txt
index 56fdb43..96e44ac 100644
--- a/builder/src/main/provisioning/cms.txt
+++ b/builder/src/main/provisioning/cms.txt
@@ -26,6 +26,7 @@
     org.apache.servicemix.bundles/org.apache.servicemix.bundles.xmlbeans/3.0.2_1
     org.apache.commons/commons-math3/3.6.1
     org.apache.commons/commons-compress/1.18
+    commons-email/commons-email/1.5
 
 [artifacts startLevel=20]
     org.apache.servicemix.bundles/org.apache.servicemix.bundles.poi/4.0.1_2
diff --git a/core/src/main/java/org/apache/sling/cms/core/internal/listeners/FileMetadataExtractor.java b/core/src/main/java/org/apache/sling/cms/core/internal/listeners/FileMetadataExtractor.java
new file mode 100644
index 0000000..d2b94a6
--- /dev/null
+++ b/core/src/main/java/org/apache/sling/cms/core/internal/listeners/FileMetadataExtractor.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.cms.core.internal.listeners;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.util.Text;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.resource.observation.ExternalResourceChangeListener;
+import org.apache.sling.api.resource.observation.ResourceChange;
+import org.apache.sling.api.resource.observation.ResourceChangeListener;
+import org.apache.sling.cms.File;
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.Property;
+import org.apache.tika.parser.AutoDetectParser;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.parser.Parser;
+import org.apache.tika.sax.BodyContentHandler;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.SAXException;
+
+/**
+ * A Resource Change Listener which extracts the metadata from sling:Files when
+ * they are uploaded.
+ */
+@Component(service = { FileMetadataExtractor.class, ResourceChangeListener.class,
+        ExternalResourceChangeListener.class }, property = { ResourceChangeListener.CHANGES + "=ADDED",
+                ResourceChangeListener.PATHS + "=/content",
+                ResourceChangeListener.PATHS + "=/static" }, immediate = true)
+public class FileMetadataExtractor implements ResourceChangeListener, ExternalResourceChangeListener {
+
+    public static final String NN_METADATA = "metadata";
+    public static final String PN_X_PARSED_BY = "X-Parsed-By";
+
+    @Reference
+    private ResourceResolverFactory factory;
+
+    private static final Logger log = LoggerFactory.getLogger(FileMetadataExtractor.class);
+
+    public void extractMetadata(File file) throws IOException, SAXException, TikaException {
+        extractMetadata(file.getResource());
+    }
+
+    public void extractMetadata(Resource resource) throws IOException, SAXException, TikaException {
+
+        log.info("Extracting metadata from {}", resource.getPath());
+        ResourceResolver resolver = resource.getResourceResolver();
+        InputStream is = resource.adaptTo(InputStream.class);
+        Resource content = resource.getChild(JcrConstants.JCR_CONTENT);
+        if (content == null) {
+            log.warn("Content resource is null");
+            return;
+        }
+        Map<String, Object> properties = new HashMap<>();
+        Resource metadata = content.getChild(NN_METADATA);
+        if (metadata != null) {
+            properties = metadata.adaptTo(ModifiableValueMap.class);
+        } else {
+            properties.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+        }
+        if (properties != null) {
+            Parser parser = new AutoDetectParser();
+            BodyContentHandler handler = new BodyContentHandler();
+            Metadata md = new Metadata();
+            ParseContext context = new ParseContext();
+            parser.parse(is, handler, md, context);
+            for (String name : md.names()) {
+                updateProperty(properties, name, md);
+            }
+            if (metadata == null) {
+                resolver.create(content, NN_METADATA, properties);
+            }
+            resolver.commit();
+            log.info("Metadata extracted from {}", resource.getPath());
+        } else {
+            log.warn("Failed to update metadata for {}", resource.getPath());
+        }
+
+    }
+
+    private void updateProperty(Map<String, Object> properties, String name, Metadata metadata) {
+        log.trace("Updating property: {}", name);
+        String filtered = Text.escapeIllegalJcrChars(name);
+        Property property = Property.get(name);
+        if (property != null) {
+            if (metadata.isMultiValued(property)) {
+                properties.put(filtered, metadata.getValues(property));
+            } else if (metadata.getDate(property) != null) {
+                Calendar cal = Calendar.getInstance();
+                cal.setTime(metadata.getDate(property));
+                properties.put(filtered, cal);
+            } else if (metadata.getInt(property) != null) {
+                properties.put(filtered, metadata.getInt(property));
+            } else {
+                properties.put(filtered, metadata.get(property));
+            }
+        } else {
+            properties.put(filtered, metadata.get(name));
+        }
+    }
+
+    @Override
+    public void onChange(List<ResourceChange> changes) {
+        Map<String, Object> serviceParams = new HashMap<>();
+        serviceParams.put(ResourceResolverFactory.SUBSERVICE, "sling-cms-metadata");
+        ResourceResolver serviceResolver = null;
+        try {
+            serviceResolver = factory.getServiceResourceResolver(serviceParams);
+            for (ResourceChange rc : changes) {
+                Resource changed = serviceResolver.getResource(rc.getPath());
+                try {
+                    extractMetadata(changed);
+                } catch (Throwable t) {
+                    log.warn("Failed to extract metadata due to exception", t);
+                }
+            }
+        } catch (LoginException e) {
+            log.error("Exception getting service user", e);
+        } finally {
+            if (serviceResolver != null) {
+                serviceResolver.close();
+            }
+        }
+
+    }
+
+}
diff --git a/core/src/test/resources/simplelogger.properties b/core/src/test/resources/simplelogger.properties
new file mode 100644
index 0000000..c33d3ba
--- /dev/null
+++ b/core/src/test/resources/simplelogger.properties
@@ -0,0 +1,17 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+org.slf4j.simpleLogger.defaultLogLevel=debug
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 445bf98..58f51d0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
     <parent>
         <groupId>org.apache.sling</groupId>
         <artifactId>sling</artifactId>
-        <version>33</version>
+        <version>34</version>
         <relativePath />
     </parent>
 
@@ -197,6 +197,12 @@
 
             <!-- Utilities / Misc -->
             <dependency>
+                <artifactId>commons-email</artifactId>
+                <version>1.5</version>
+                <groupId>org.apache.commons</groupId>
+                <scope>provided</scope>
+            </dependency>
+            <dependency>
                 <groupId>org.apache.commons</groupId>
                 <artifactId>commons-lang3</artifactId>
                 <version>3.4</version>
diff --git a/reference/pom.xml b/reference/pom.xml
index 60055aa..ecf8b42 100644
--- a/reference/pom.xml
+++ b/reference/pom.xml
@@ -4,7 +4,9 @@
     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. -->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
+>
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <artifactId>org.apache.sling.cms</artifactId>
@@ -52,7 +54,8 @@
                             jcr_root/static/clientlibs/reference;overwrite=true;ignoreImportProviders:=xml;path:=/static/clientlibs/reference
                         </Sling-Initial-Content>
                         <Sling-Model-Packages>
-                            org.apache.sling.cms.reference.models
+                            org.apache.sling.cms.reference.models,
+                            org.apache.sling.cms.reference.forms.impl,
                         </Sling-Model-Packages>
                     </instructions>
                 </configuration>
@@ -63,7 +66,7 @@
     <dependencies>
         <dependency>
             <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.cms.core</artifactId>
+            <artifactId>org.apache.sling.cms.api</artifactId>
             <version>${project.version}</version>
         </dependency>
         <dependency>
@@ -137,6 +140,28 @@
             <artifactId>jsp-api</artifactId>
             <version>2.0</version>
         </dependency>
+        <dependency>
+            <artifactId>commons-email</artifactId>
+            <groupId>org.apache.commons</groupId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.event.api</artifactId>
+        </dependency>
+
+        <!-- Testing dependencies -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.testing.sling-mock.junit4</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+        </dependency>
     </dependencies>
 
     <profiles>
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/FieldHandler.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/FieldHandler.java
new file mode 100644
index 0000000..1dc7279
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/FieldHandler.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.cms.reference.forms;
+
+import java.util.Map;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * A service interface for registering services to handle a form field.
+ */
+public interface FieldHandler {
+
+    /**
+     * Returns true if the FieldHandler should handle the field resource
+     * 
+     * @param fieldResource the field resource to handle
+     * @return true if the FieldHandler will handle, false otherwise
+     */
+    boolean handles(Resource fieldResource);
+
+    /**
+     * Handle the field being submitted. Uses the configuration from the
+     * fieldResource and the data from the request and saves the value into the
+     * formData.
+     * 
+     * @param request       the request for the form submission
+     * @param fieldResource the resource from which to get the field configuration
+     * @param formData      the Map to which to save the data for the field
+     * @throws FormException an exception occurs attempting to handle the field
+     *                       including the field not being set or being invalid
+     */
+    void handleField(SlingHttpServletRequest request, Resource fieldResource, Map<String, Object> formData)
+            throws FormException;
+
+    static boolean isRequired(Resource fieldResource) {
+        return fieldResource.getValueMap().get("required", false);
+    }
+
+    static String getName(Resource fieldResource) {
+        return fieldResource.getValueMap().get("name", String.class);
+    }
+}
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/FormAction.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/FormAction.java
new file mode 100644
index 0000000..8458b47
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/FormAction.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.cms.reference.forms;
+
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * A service interface for registering form actions.
+ */
+public interface FormAction {
+
+    /**
+     * Handle a form submission. The form can be assumed to have been validated.
+     * 
+     * @param actionResource the configuration to use to configure the form action
+     * @param request        the form request to handle
+     * @return the result of the action
+     * @throws FormException an exception occurs handling the form
+     */
+    FormActionResult handleForm(Resource actionResource, FormRequest request) throws FormException;
+
+    /**
+     * Checks if the the Form Action should handle the specified request and action
+     * 
+     * @param actionResource the resource to check
+     * @return true if this FormAction should handle the configuration, false
+     *         otherwise
+     */
+    boolean handles(Resource actionResource);
+}
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/FormActionResult.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/FormActionResult.java
new file mode 100644
index 0000000..cea8bfa
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/FormActionResult.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.cms.reference.forms;
+
+/**
+ * A simple Pojo for the result of a form action.
+ */
+public class FormActionResult {
+
+    public static final FormActionResult failure(String message) {
+        return new FormActionResult(false, message);
+    }
+
+    public static final FormActionResult success(String message) {
+        return new FormActionResult(true, message);
+    }
+
+    private final String message;
+
+    private final boolean succeeded;
+
+    private FormActionResult(boolean succeeded, String message) {
+        this.succeeded = succeeded;
+        this.message = message;
+    }
+
+    /**
+     * @return the message
+     */
+    public String getMessage() {
+        return message;
+    }
+
+    /**
+     * @return the succeeded
+     */
+    public boolean isSucceeded() {
+        return succeeded;
+    }
+
+}
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/FormException.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/FormException.java
new file mode 100644
index 0000000..c6ca77b
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/FormException.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.cms.reference.forms;
+
+/**
+ * An exception to indicate problems occurred with form processing.
+ */
+public class FormException extends Exception {
+
+    private static final long serialVersionUID = 3238204372500251994L;
+
+    /**
+     * Construct a new FormException with only a message
+     * 
+     * @param message the message to indicate the cause of the exception
+     */
+    public FormException(String message) {
+        super(message);
+    }
+
+}
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/FormRequest.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/FormRequest.java
new file mode 100644
index 0000000..6c8a198
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/FormRequest.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.cms.reference.forms;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+
+/**
+ * A service interface for registering form actions. Each implementation class
+ * should be registered with an "actionResourceType" property to link the
+ * configuration resource to the implementation for executing the action.
+ */
+public interface FormRequest {
+
+    ValueMap getFormData();
+
+    Resource getFormResource();
+
+    SlingHttpServletRequest getOriginalRequest();
+    
+    String getSessionId();
+}
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/FormValueProvider.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/FormValueProvider.java
new file mode 100644
index 0000000..b385780
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/FormValueProvider.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.cms.reference.forms;
+
+import java.util.Map;
+
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * Service interface for providing values to form fields. Classes implementing
+ * this interface should make values available to the form fields via the Form
+ * Data in the Form Request.
+ */
+public interface FormValueProvider {
+
+    boolean handles(Resource valueProviderResource);
+
+    /**
+     * Populates the form values for a request.
+     * 
+     * @param valueProviderResource the resource for configuring this provider
+     * @param formData              the map of data for the form
+     */
+    void loadValues(Resource valueProviderResource, Map<String, Object> formData);
+}
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/FormHandler.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/FormHandler.java
new file mode 100644
index 0000000..3a36daf
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/FormHandler.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.cms.reference.forms.impl;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.servlets.SlingAllMethodsServlet;
+import org.apache.sling.cms.Page;
+import org.apache.sling.cms.PageManager;
+import org.apache.sling.cms.ResourceTree;
+import org.apache.sling.cms.reference.forms.FormAction;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.apache.sling.cms.reference.forms.FormRequest;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(service = Servlet.class, property = { "sling.servlet.resourceTypes=reference/components/forms/form",
+        "sling.servlet.methods=POST", "sling.servlet.extensions=html", "sling.servlet.selectors=allowpost" })
+public class FormHandler extends SlingAllMethodsServlet {
+
+    private static final Logger log = LoggerFactory.getLogger(FormHandler.class);
+
+    @Reference(policyOption = ReferencePolicyOption.GREEDY)
+    private List<FormAction> formActions;
+
+    private static final long serialVersionUID = -8149443208959899098L;
+
+    @Override
+    protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response)
+            throws ServletException, IOException {
+
+        String pagePath = Optional.ofNullable(request.getResource().adaptTo(PageManager.class))
+                .map(PageManager::getPage).map(Page::getPath)
+                .orElse(StringUtils.substringBefore(request.getResource().getPath(), "/" + JcrConstants.JCR_CONTENT));
+
+        List<Resource> actionResources = ResourceTree.stream(request.getResource().getChild("actions"))
+                .map(ResourceTree::getResource).collect(Collectors.toList());
+
+        try {
+            FormRequest formRequest = getFormRequest(request);
+            if (formRequest == null) {
+                log.warn("Unable to create form request");
+                response.sendRedirect(request.getResourceResolver().map(request, pagePath) + ".html?error=fields");
+                return;
+            }
+            request.getSession().setAttribute(formRequest.getSessionId(), formRequest.getFormData());
+            for (Resource actionResource : actionResources) {
+                log.debug("Finding action handler for: {}", actionResource);
+                for (FormAction action : formActions) {
+                    if (action.handles(actionResource)) {
+                        log.debug("Invoking handler: {}", action.getClass());
+                        action.handleForm(actionResource, formRequest);
+                        break;
+                    }
+                }
+            }
+            request.getSession().removeAttribute(formRequest.getSessionId());
+        } catch (FormException e) {
+            log.warn("Exception executing actions", e);
+            response.sendRedirect(request.getResourceResolver().map(request, pagePath) + ".html?error=actions");
+            return;
+        }
+
+        String thankYouPage = request.getResource().getValueMap().get("thankYouPage", String.class);
+        if (StringUtils.isNotBlank(thankYouPage)) {
+            response.sendRedirect(request.getResourceResolver().map(request, thankYouPage) + ".html?message=success");
+        } else {
+            response.sendRedirect(request.getResourceResolver().map(request, pagePath) + ".html?message=success");
+        }
+    }
+
+    protected FormRequest getFormRequest(SlingHttpServletRequest request) throws FormException {
+        FormRequest fr = request.adaptTo(FormRequest.class);
+        ((FormRequestImpl) fr).initFields();
+        return fr;
+    }
+}
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/FormRequestImpl.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/FormRequestImpl.java
new file mode 100644
index 0000000..a5bcef3
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/FormRequestImpl.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.cms.reference.forms.impl;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.annotation.PostConstruct;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.wrappers.ValueMapDecorator;
+import org.apache.sling.cms.ResourceTree;
+import org.apache.sling.cms.reference.forms.FieldHandler;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.apache.sling.cms.reference.forms.FormRequest;
+import org.apache.sling.cms.reference.forms.FormValueProvider;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy;
+import org.apache.sling.models.annotations.injectorspecific.OSGiService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Model(adaptables = { SlingHttpServletRequest.class, Resource.class }, adapters = FormRequest.class)
+public class FormRequestImpl implements FormRequest {
+
+    private static final Logger log = LoggerFactory.getLogger(FormRequestImpl.class);
+
+    @OSGiService(injectionStrategy = InjectionStrategy.OPTIONAL)
+    private List<FieldHandler> fieldHandlers;
+
+    private Map<String, Object> formData = new HashMap<>();
+
+    @OSGiService(injectionStrategy = InjectionStrategy.OPTIONAL)
+    private List<FormValueProvider> formValueProvider;
+
+    private final boolean loadProviders;
+
+    private SlingHttpServletRequest request;
+
+    public FormRequestImpl(SlingHttpServletRequest request) throws FormException {
+        this.request = request;
+        this.loadProviders = true;
+    }
+
+    public FormRequestImpl(SlingHttpServletRequest request, boolean loadProviders) throws FormException {
+        this.request = request;
+        this.loadProviders = loadProviders;
+    }
+
+    @Override
+    public ValueMap getFormData() {
+        return new ValueMapDecorator(formData);
+    }
+
+    @Override
+    public Resource getFormResource() {
+        return request.getResource();
+    }
+
+    @Override
+    public SlingHttpServletRequest getOriginalRequest() {
+        return request;
+    }
+
+    @SuppressWarnings("unchecked")
+    @PostConstruct
+    public void init() throws FormException {
+        if (request.getSession().getAttribute(this.getSessionId()) != null) {
+            formData.putAll(((Map<String, Object>) request.getSession().getAttribute(this.getSessionId())));
+        }
+        if (this.loadProviders) {
+            List<Resource> providers = ResourceTree.stream(getFormResource().getChild("providers"))
+                    .map(ResourceTree::getResource).collect(Collectors.toList());
+            for (Resource provider : providers) {
+                log.debug("Looking for handler for: {}", provider);
+                for (FormValueProvider fvp : formValueProvider) {
+                    if (fvp.handles(provider)) {
+                        log.debug("Invoking field value provider: {}", fvp.getClass());
+                        fvp.loadValues(provider, formData);
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+    public void initFields() throws FormException {
+        List<Resource> fields = ResourceTree.stream(getFormResource().getChild("fields")).map(ResourceTree::getResource)
+                .collect(Collectors.toList());
+        for (Resource field : fields) {
+            log.debug("Looking for handler for: {}", field);
+            for (FieldHandler fieldHandler : fieldHandlers) {
+                if (fieldHandler.handles(field)) {
+                    log.debug("Invoking field handler: {}", fieldHandler.getClass());
+                    fieldHandler.handleField(request, field, formData);
+                    break;
+                }
+            }
+        }
+    }
+
+    @Override
+    public String getSessionId() {
+        return "errorval-" + this.getOriginalRequest().getResource().getPath();
+    }
+
+}
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/SendEmailAction.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/SendEmailAction.java
new file mode 100644
index 0000000..a66d457
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/SendEmailAction.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.cms.reference.forms.impl.actions;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.lang3.text.StrSubstitutor;
+import org.apache.commons.mail.DefaultAuthenticator;
+import org.apache.commons.mail.Email;
+import org.apache.commons.mail.EmailException;
+import org.apache.commons.mail.SimpleEmail;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.cms.reference.forms.FormAction;
+import org.apache.sling.cms.reference.forms.FormActionResult;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.apache.sling.cms.reference.forms.FormRequest;
+import org.apache.sling.event.jobs.Job;
+import org.apache.sling.event.jobs.JobManager;
+import org.apache.sling.event.jobs.consumer.JobConsumer;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.metatype.annotations.Designate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(service = { FormAction.class, JobConsumer.class }, property = {
+        JobConsumer.PROPERTY_TOPICS + "=" + SendEmailAction.TOPIC })
+@Designate(ocd = SendEmailActonConfig.class)
+public class SendEmailAction implements JobConsumer, FormAction {
+
+    public static final String FROM = "from";
+    private static final Logger log = LoggerFactory.getLogger(SendEmailAction.class);
+
+    public static final String MESSAGE = "message";
+    public static final String SUBJECT = "subject";
+    public static final String TO = "to";
+    public static final String TOPIC = "reference/form/sendemail";
+
+    private SendEmailActonConfig config;
+
+    @Reference
+    private JobManager jobManager;
+
+    @Activate
+    public void activate(SendEmailActonConfig config) {
+        this.config = config;
+    }
+
+    @Override
+    public FormActionResult handleForm(Resource actionResource, FormRequest request) throws FormException {
+        StrSubstitutor sub = new StrSubstitutor(request.getFormData());
+
+        ValueMap properties = actionResource.getValueMap();
+        String to = sub.replace(properties.get(TO, String.class));
+        log.debug("Queueing contact message to {}", to);
+
+        Map<String, Object> data = new HashMap<>();
+        data.put(SendEmailAction.SUBJECT, sub.replace(properties.get(SUBJECT, String.class)));
+        data.put(SendEmailAction.MESSAGE, sub.replace(properties.get(MESSAGE, String.class)));
+        data.put(SendEmailAction.TO, to);
+        data.put(SendEmailAction.FROM, sub.replace(properties.get(FROM, String.class)));
+        jobManager.addJob(TOPIC, data);
+        log.debug("Job queued successfully!");
+        return FormActionResult.success("Email queued successfully!");
+    }
+
+    @Override
+    public boolean handles(Resource actionResource) {
+        return "reference/components/forms/actions/sendemail".equals(actionResource.getResourceType());
+    }
+
+    public JobResult process(final Job job) {
+        log.trace("process");
+
+        try {
+            Email email = new SimpleEmail();
+            email.setHostName(config.hostName());
+            email.setSmtpPort(config.smtpPort());
+            email.setAuthenticator(new DefaultAuthenticator(config.username(), config.password()));
+            email.setStartTLSEnabled(config.tlsEnabled());
+            log.debug("Configuring connection to {}:{} with username {}", config.hostName(), config.smtpPort(),
+                    config.username());
+
+            String from = job.getProperty(FROM, String.class);
+            String to = job.getProperty(TO, String.class);
+            String subject = job.getProperty(SUBJECT, String.class);
+            String message = job.getProperty(MESSAGE, String.class);
+
+            email.setFrom(from);
+            email.setSubject(job.getProperty(SUBJECT, String.class));
+            email.setMsg(message);
+            email.addTo(to);
+            log.debug("Sending email from {} to {} with subject {}", from, to, subject);
+
+            email.send();
+        } catch (EmailException e) {
+            log.warn("Exception sending email for job " + job.getId(), e);
+            return JobResult.FAILED;
+        }
+
+        // process the job and return the result
+        return JobResult.OK;
+    }
+}
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/SendEmailActonConfig.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/SendEmailActonConfig.java
new file mode 100644
index 0000000..4a16377
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/SendEmailActonConfig.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.cms.reference.forms.impl.actions;
+
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.AttributeType;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+@ObjectClassDefinition(name = "%cms.reference.sendemail.name", description = "%cms.reference.sendemail.description", localization = "OSGI-INF/l10n/bundle")
+public @interface SendEmailActonConfig {
+    
+    @AttributeDefinition(name = "%cms.reference.sendemail.hostName.name", description = "%cms.reference.sendemail.hostName.description")
+    String hostName();
+
+    @AttributeDefinition(name = "%cms.reference.sendemail.smtpPort.name", description = "%cms.reference.sendemail.smtpPort.description")
+    int smtpPort();
+
+    @AttributeDefinition(name = "%cms.reference.sendemail.tlsEnabled.name", description = "%cms.reference.sendemail.tlsEnabled.description")
+    boolean tlsEnabled();
+
+    @AttributeDefinition(name = "%cms.reference.sendemail.username.name", description = "%cms.reference.sendemail.username.description")
+    String username();
+
+    @AttributeDefinition(name = "%cms.reference.sendemail.password.name", description = "%cms.reference.sendemail.password.description", type=AttributeType.PASSWORD)
+    String password();
+}
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/UpdateProfileAction.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/UpdateProfileAction.java
new file mode 100644
index 0000000..09fa95c
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/UpdateProfileAction.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.cms.reference.forms.impl.actions;
+
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Map.Entry;
+import java.util.stream.Collectors;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.cms.reference.forms.FormAction;
+import org.apache.sling.cms.reference.forms.FormActionResult;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.apache.sling.cms.reference.forms.FormRequest;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(service = FormAction.class)
+public class UpdateProfileAction implements FormAction {
+
+    private static final Logger log = LoggerFactory.getLogger(UpdateProfileAction.class);
+
+    @Override
+    public FormActionResult handleForm(Resource actionResource, FormRequest request) throws FormException {
+        ResourceResolver resolver = request.getOriginalRequest().getResourceResolver();
+        String userId = resolver.getUserID();
+        JackrabbitSession session = (JackrabbitSession) resolver.adaptTo(Session.class);
+
+        try {
+            final UserManager userManager = session.getUserManager();
+            if (userManager.getAuthorizable(userId) != null) {
+
+                User user = (User) userManager.getAuthorizable(userId);
+                log.debug("Updating profile for {}", userId);
+
+                String subpath = actionResource.getValueMap().get("subpath", "profile");
+                ValueFactory valueFactory = session.getValueFactory();
+
+                for (Entry<String, Object> e : request.getFormData().entrySet()) {
+                    Value value = null;
+                    if (e.getValue() instanceof String[]) {
+                        user.setProperty(subpath + "/" + e.getKey(), Arrays.stream((String[]) e.getValue())
+                                .map(valueFactory::createValue).collect(Collectors.toList()).toArray(new Value[0]));
+                    } else {
+                        if (e.getValue() instanceof Calendar) {
+                            value = valueFactory.createValue((Calendar) e.getValue());
+                        } else if (e.getValue() instanceof Double) {
+                            value = valueFactory.createValue((Double) e.getValue());
+                        } else if (e.getValue() instanceof Integer) {
+                            value = valueFactory.createValue((Double) e.getValue());
+                        } else {
+                            value = valueFactory.createValue((String) e.getValue());
+                        }
+                        user.setProperty(subpath + "/" + e.getKey(), value);
+                    }
+                }
+                log.debug("Saving changes!");
+                resolver.commit();
+
+                return FormActionResult.success("Profile Updated");
+            } else {
+                log.warn("No profile found for {}", userId);
+                return FormActionResult.failure("No profile found for " + userId);
+            }
+        } catch (RepositoryException | PersistenceException e) {
+            log.warn("Failed to update profile for {}", userId, e);
+            return FormActionResult.failure("Failed to update profile for " + userId);
+        }
+    }
+
+    @Override
+    public boolean handles(Resource actionResource) {
+        return "reference/components/forms/actions/updateprofile".equals(actionResource.getResourceType());
+    }
+
+}
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/fields/SelectionHandler.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/fields/SelectionHandler.java
new file mode 100644
index 0000000..955197b
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/fields/SelectionHandler.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.cms.reference.forms.impl.fields;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.cms.reference.forms.FieldHandler;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(service = FieldHandler.class)
+public class SelectionHandler implements FieldHandler {
+
+    private static final Logger log = LoggerFactory.getLogger(SelectionHandler.class);
+
+    @Override
+    public boolean handles(Resource fieldResource) {
+        return "reference/components/forms/fields/selection".equals(fieldResource.getResourceType());
+    }
+
+    @Override
+    public void handleField(SlingHttpServletRequest request, Resource fieldResource, Map<String, Object> formData)
+            throws FormException {
+        log.trace("handleField");
+        String name = FieldHandler.getName(fieldResource);
+
+        if (isMultiple(fieldResource)) {
+            String[] value = stripBlank(request.getParameterValues(name));
+            if (value.length == 0) {
+                if (FieldHandler.isRequired(fieldResource)) {
+                    throw new FormException("Field " + name + " not set and is required");
+                } else {
+                    log.debug("Ignoring unset value: {}", name);
+                }
+            } else {
+                log.debug("Setting value for: {}", name);
+                formData.put(name, value);
+            }
+        } else {
+            String value = request.getParameter(name);
+            if (StringUtils.isBlank(value)) {
+                if (FieldHandler.isRequired(fieldResource)) {
+                    throw new FormException("Field " + name + " not set and is required");
+                } else {
+                    log.debug("Ignoring unset value: {}", name);
+                }
+            } else {
+                log.debug("Setting value for: {}", name);
+                formData.put(name, value);
+            }
+        }
+    }
+
+    private String[] stripBlank(String[] parameterValues) {
+        return Optional.ofNullable(parameterValues).map(v -> {
+            List<String> values = Arrays.stream(v).filter(StringUtils::isNotBlank).collect(Collectors.toList());
+            return values.toArray(new String[values.size()]);
+        }).orElse(new String[0]);
+    }
+
+    private boolean isMultiple(Resource fieldResource) {
+        return fieldResource.getValueMap().get("multiple", false);
+    }
+
+}
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/fields/TextareaHandler.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/fields/TextareaHandler.java
new file mode 100644
index 0000000..26b0036
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/fields/TextareaHandler.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.cms.reference.forms.impl.fields;
+
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.cms.reference.forms.FieldHandler;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TextareaHandler implements FieldHandler {
+
+    private static final Logger log = LoggerFactory.getLogger(TextareaHandler.class);
+
+    @Override
+    public boolean handles(Resource fieldResource) {
+        String resourceType = fieldResource.getResourceType();
+        return "reference/components/forms/fields/textarea".equals(resourceType);
+    }
+
+    @Override
+    public void handleField(SlingHttpServletRequest request, Resource fieldResource, Map<String, Object> formData)
+            throws FormException {
+        log.trace("handleField");
+        String name = FieldHandler.getName(fieldResource);
+        String value = request.getParameter(name);
+        if (StringUtils.isBlank(value)) {
+            if (FieldHandler.isRequired(fieldResource)) {
+                throw new FormException("Field " + name + " not set and is required");
+            } else {
+                log.debug("Ignoring unset value: {}", name);
+            }
+        } else {
+            log.debug("Setting value for: {}", name);
+            formData.put(name, value);
+        }
+    }
+
+}
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/fields/TextfieldHandler.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/fields/TextfieldHandler.java
new file mode 100644
index 0000000..2f58dd8
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/fields/TextfieldHandler.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.cms.reference.forms.impl.fields;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.cms.reference.forms.FieldHandler;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(service = FieldHandler.class)
+public class TextfieldHandler implements FieldHandler {
+
+    private static final Logger log = LoggerFactory.getLogger(TextfieldHandler.class);
+    private static final Map<String, String> typePatterns = new HashMap<>();
+    static {
+        typePatterns.put("date", "\\d{4}-\\d{2}-\\d{2}");
+        typePatterns.put("datetime-local", "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}");
+        typePatterns.put("email", ".+@.+");
+        typePatterns.put("time", "\\d{2}:\\d{2}(:\\d{2})?");
+    }
+
+    private static final Map<String, String> dateFormats = new HashMap<>();
+    static {
+        dateFormats.put("date", "yyyy-MM-dd");
+        dateFormats.put("datetime-local", "yyyy-MM-ddThh:mm");
+    }
+
+    @Override
+    public boolean handles(Resource fieldResource) {
+        String resourceType = fieldResource.getResourceType();
+        return "reference/components/forms/fields/textfield".equals(resourceType);
+    }
+
+    @Override
+    public void handleField(SlingHttpServletRequest request, Resource fieldResource, Map<String, Object> formData)
+            throws FormException {
+        log.trace("handleField");
+        String name = FieldHandler.getName(fieldResource);
+        String value = request.getParameter(name);
+        if (StringUtils.isBlank(value)) {
+            if (FieldHandler.isRequired(fieldResource)) {
+                throw new FormException("Field " + name + " not set and is required");
+            } else {
+                log.debug("Ignoring unset value: {}", name);
+            }
+        } else {
+            validateValue(fieldResource, value);
+            log.debug("Setting value for: {}", name);
+
+            String saveAs = fieldResource.getValueMap().get("saveAs", "string");
+
+            if ("date".equals(saveAs)) {
+
+                String type = fieldResource.getValueMap().get("type", String.class);
+                if (!dateFormats.containsKey(type)) {
+                    throw new FormException("Field " + name + " is not a date type");
+                }
+                try {
+                    Calendar cal = Calendar.getInstance();
+                    cal.setTime(new SimpleDateFormat(dateFormats.get(type)).parse(value));
+                    formData.put(name, cal);
+                } catch (ParseException e) {
+                    throw new FormException("Failed to parse date from " + value);
+                }
+            } else if ("double".equals(saveAs)) {
+                try {
+                    formData.put(name, Double.parseDouble(value));
+                } catch (NumberFormatException nfe) {
+                    throw new FormException("Failed to parse double from " + value);
+                }
+            } else if ("integer".equals(saveAs)) {
+                try {
+                    formData.put(name, Integer.parseInt(value, 10));
+                } catch (NumberFormatException nfe) {
+                    throw new FormException("Failed to parse integer from " + value);
+                }
+            } else {
+                formData.put(name, value);
+            }
+        }
+
+    }
+
+    protected void validateValue(Resource fieldResource, String value) throws FormException {
+        String pattern = fieldResource.getValueMap().get("pattern", String.class);
+        if (StringUtils.isNotBlank(pattern) && !value.matches(pattern)) {
+            throw new FormException(
+                    "Field " + FieldHandler.getName(fieldResource) + " does not match pattern " + pattern);
+        }
+        String type = fieldResource.getValueMap().get("type", String.class);
+        if (typePatterns.containsKey(type) && !value.matches(typePatterns.get(type))) {
+            throw new FormException("Field " + FieldHandler.getName(fieldResource) + " is not a valid " + type);
+        }
+
+        if ("number".equals(type) && !NumberUtils.isNumber(value)) {
+            throw new FormException("Field " + FieldHandler.getName(fieldResource) + " is not a number");
+        }
+    }
+
+}
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/providers/UserProfileFormValueProvider.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/providers/UserProfileFormValueProvider.java
new file mode 100644
index 0000000..a37023d
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/providers/UserProfileFormValueProvider.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.cms.reference.forms.impl.providers;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.cms.reference.forms.FormValueProvider;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(service = FormValueProvider.class)
+public class UserProfileFormValueProvider implements FormValueProvider {
+
+    private static final Logger log = LoggerFactory.getLogger(UserProfileFormValueProvider.class);
+
+    @Override
+    public void loadValues(Resource providerResource, Map<String, Object> formData) {
+        log.trace("loadFormData");
+        try {
+            ResourceResolver resolver = providerResource.getResourceResolver();
+            String userId = resolver.getUserID();
+            JackrabbitSession session = (JackrabbitSession) resolver.adaptTo(Session.class);
+            UserManager userManager = session.getUserManager();
+            User user = (User) userManager.getAuthorizable(userId);
+
+            String subpath = providerResource.getValueMap().get("subpath", "profile");
+            log.debug("Loading profile data from: {}/{}", user.getPath(), subpath);
+
+            Iterator<String> keys = user.getPropertyNames(subpath);
+            while (keys.hasNext()) {
+
+                String key = keys.next();
+                log.debug("Loading key {}", key);
+                loadKey(formData, subpath, key, user);
+            }
+
+        } catch (RepositoryException e) {
+            log.warn("Exception loading values from user profile", e);
+        }
+    }
+
+    private void loadKey(Map<String, Object> formData, String subpath, String key, User user) {
+        Object value = null;
+
+        try {
+            Value[] v = user.getProperty(subpath + "/" + key);
+            if (v.length > 1) {
+                value = Arrays.stream(v).map(t -> {
+                    try {
+                        return t.getString();
+                    } catch (IllegalStateException | RepositoryException e) {
+                        log.warn("Failed to get string value for " + key, e);
+                        return null;
+                    }
+                }).collect(Collectors.toList()).toArray(new String[0]);
+            } else if (v[0].getType() == PropertyType.LONG) {
+                value = v[0].getLong();
+            } else if (v[0].getType() == PropertyType.DOUBLE) {
+                value = v[0].getDouble();
+            } else if (v[0].getType() == PropertyType.DATE) {
+                value = v[0].getDate();
+            } else {
+                value = v[0].getString();
+            }
+            formData.put(key, value);
+        } catch (RepositoryException e) {
+            log.warn("Failed to get string value for " + key, e);
+        }
+    }
+
+    @Override
+    public boolean handles(Resource valueProviderResource) {
+        return "reference/components/forms/providers/userprofile".equals(valueProviderResource.getResourceType());
+    }
+}
diff --git a/reference/src/main/resources/OSGI-INF/l10n/bundle.properties b/reference/src/main/resources/OSGI-INF/l10n/bundle.properties
index 6141e55..0911dc9 100644
--- a/reference/src/main/resources/OSGI-INF/l10n/bundle.properties
+++ b/reference/src/main/resources/OSGI-INF/l10n/bundle.properties
@@ -29,4 +29,30 @@ Search component
 
 searchServiceUsername.name=Search Service User Name
 searchServiceUsername.description=The name of a service user to use \
-to use for searching, if not specified, the current user will be used
\ No newline at end of file
+to use for searching, if not specified, the current user will be used
+
+# Send Email
+cms.reference.sendemail.name=Apache Sling Reference Send Email Form Action
+cms.reference.sendemail.description=A reference form action for sending a \
+simple text email
+
+cms.reference.sendemail.hostName.name=SMTP Host Name
+cms.reference.sendemail.hostName.description=The name of the SMTP post to \
+connect to
+
+cms.reference.sendemail.smtpPort.name=SMTP Port
+cms.reference.sendemail.smtpPort.description=The port upon which to connect \
+to the SMTP server
+
+
+cms.reference.sendemail.tlsEnabled.name=TLS Enabled
+cms.reference.sendemail.tlsEnabled.description=Whether or not TLS security is \
+enabled
+
+cms.reference.sendemail.username.name=Username
+cms.reference.sendemail.username.description=The username with which to connect \
+to the SMTP server
+
+cms.reference.sendemail.password.name=Password
+cms.reference.sendemail.password.description=The password to use when connecting \
+to the SMTP server
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/sendemail.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/sendemail.json
new file mode 100644
index 0000000..2f8e0ad
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/sendemail.json
@@ -0,0 +1,5 @@
+{
+    "jcr:primaryType" : "sling:Component",
+    "jcr:title": "Email",
+    "componentType": "Form Action"
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/sendemail/edit.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/sendemail/edit.json
new file mode 100644
index 0000000..bee06bd
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/sendemail/edit.json
@@ -0,0 +1,37 @@
+ {
+    "jcr:primaryType": "nt:unstructured",
+    "sling:resourceType": "sling-cms/components/editor/slingform",
+    "button": "Save",
+    "fields": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/general/container",
+        "from": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "From",
+            "name": "from",
+            "required": true
+        },
+        "to": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "To",
+            "name": "to",
+            "required": true
+        },
+        "subject": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Subject",
+            "name": "subject",
+            "required": true
+        },
+        "message": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/textarea",
+            "label": "Message",
+            "name": "message",
+            "required": true
+        }
+    }
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/sendemail/sendemail.jsp b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/sendemail/sendemail.jsp
new file mode 100644
index 0000000..b83654d
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/sendemail/sendemail.jsp
@@ -0,0 +1,32 @@
+<%-- /*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */ --%>
+ <%@include file="/libs/sling-cms/global.jsp"%>
+<c:if test="${cmsEditEnabled == 'true'}">
+    <h3>Send Email</h3>
+    <dl>
+        <dt>From</dt>
+        <dd>${properties.from}</dd>
+        <dt>To</dt>
+        <dd>${properties.to}</dd>
+        <dt>Subject</dt>
+        <dd>${properties.subject}</dd>
+        <dt>Message</dt>
+        <dd>${properties.message}</dd>
+    </dl>
+</c:if>
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/updateprofile.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/updateprofile.json
new file mode 100644
index 0000000..8549c19
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/updateprofile.json
@@ -0,0 +1,5 @@
+{
+    "jcr:primaryType" : "sling:Component",
+    "jcr:title": "Update Profile",
+    "componentType": "Form Action"
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/updateprofile/edit.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/updateprofile/edit.json
new file mode 100644
index 0000000..b004f7f
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/updateprofile/edit.json
@@ -0,0 +1,15 @@
+ {
+    "jcr:primaryType": "nt:unstructured",
+    "sling:resourceType": "sling-cms/components/editor/slingform",
+    "button": "Save",
+    "fields": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/general/container",
+        "subpath": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Subpath",
+            "name": "subpath"
+        }
+    }
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/updateprofile/updateprofile.jsp b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/updateprofile/updateprofile.jsp
new file mode 100644
index 0000000..d6f7d05
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/updateprofile/updateprofile.jsp
@@ -0,0 +1,26 @@
+<%-- /*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */ --%>
+ <%@include file="/libs/sling-cms/global.jsp"%>
+<c:if test="${cmsEditEnabled == 'true'}">
+    <h3>Update Profile</h3>
+    <dl>
+        <dt>Subpath</dt>
+        <dd>${properties.subpath}</dd>
+    </dl>
+</c:if>
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/selection.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/selection.json
new file mode 100644
index 0000000..d3f03c2
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/selection.json
@@ -0,0 +1,5 @@
+{
+    "jcr:primaryType" : "sling:Component",
+    "jcr:title": "Selection",
+    "componentType": "Form Field"
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/selection/edit.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/selection/edit.json
new file mode 100644
index 0000000..a3cfcfa
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/selection/edit.json
@@ -0,0 +1,103 @@
+ {
+    "jcr:primaryType": "nt:unstructured",
+    "sling:resourceType": "sling-cms/components/editor/slingform",
+    "button": "Save",
+    "fields": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/general/container",
+        "label": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Label",
+            "name": "label"
+        },
+        "name": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Name",
+            "name": "name",
+            "required": true
+        },
+        "multiple": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/select",
+            "label": "Multiple Select?",
+            "name": "multiple",
+            "options": {
+                "yes": {
+                    "label": "Yes",
+                    "value": true
+                },
+                "no": {
+                    "label": "No",
+                    "value": false
+                }
+            }
+        },
+        "multipleTypeHint": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+            "name": "multiple@TypeHint",
+            "value": "Boolean"
+        },
+        "noSelection": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "No Selection Message",
+            "name": "noSelection"
+        },
+        "tagRoot": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/path",
+            "label": "Tag Root",
+            "name": "tagRoot",
+            "basePath": "/etc/taxonomy",
+            "type": "sling:Taxonomy",
+            "titleProperty": "jcr:title"
+        },
+        "required": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/select",
+            "label": "Required",
+            "name": "required",
+            "options": {
+                "no": {
+                    "label": "No",
+                    "value": false
+                },
+                "yes": {
+                    "label": "Yes",
+                    "value": true
+                }
+            }
+        },
+        "requiredTypeHint": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+            "name": "required@TypeHint",
+            "value": "Boolean"
+        },
+        "display": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/select",
+            "label": "Display",
+            "name": "display",
+            "options": {
+                "select": {
+                    "label": "Select",
+                    "value": "select"
+                },
+                "radioCheckbox": {
+                    "label": "Radio / Checkbox",
+                    "value": "radioCheckbox"
+                }
+            }
+        },
+        "addClasses": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Add Classes",
+            "name": "addClasses"
+        }
+    }
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/selection/selection.jsp b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/selection/selection.jsp
new file mode 100644
index 0000000..ce84bb4
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/selection/selection.jsp
@@ -0,0 +1,86 @@
+<%-- /*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */ --%>
+<%@include file="/libs/sling-cms/global.jsp"%>
+<c:if test="${not empty properties.tagRoot}">
+    <c:set var="children" value="${sling:listChildren(sling:getResource(resourceResolver,properties.tagRoot))}" />
+    <div class="${formConfig.fieldGroupClass} ${properties.addClasses}">
+        <c:if test="${not empty properties.label}">
+            <label for="${properties.name}">
+                <sling:encode value="${properties.label}" mode="HTML" />
+                <c:if test="${properties.required}">
+                    <span class="${formConfig.fieldRequiredClass}">
+                        *
+                    </span>
+                </c:if>
+            </label>
+        </c:if>
+        <c:choose>
+            <c:when test="${properties.display == 'radioCheckbox'}">
+                <c:forEach var="tag" items="${children}">
+                    <div class="${formConfig.checkFieldClass}">
+                        <c:set var="selected" value="${false}" />
+                        <c:choose>
+                            <c:when test="${properties.multiple}">
+                                <c:forEach var="val" items="${formData[properties.name]}">
+                                    <c:if test="${val == tag.name}">
+                                        <c:set var="selected" value="${true}" />
+                                    </c:if>
+                                </c:forEach>
+                            </c:when>
+                            <c:when test="${formData[properties.name] == tag.name}">
+                                <c:set var="selected" value="${true}" />
+                            </c:when>
+                        </c:choose>
+                        <input class="${formConfig.checkInputClass}" type="${properties.multiple ? 'checkbox' : 'radio'}" name="${properties.name}" id="${properties.name}-${tag.name}" value="${tag.name}" ${selected ? 'checked="checked"' : ''} />
+                        <label class="${formConfig.checkLabelClass}" for="${properties.name}-${tag.name}">
+                            <sling:encode value="${tag.valueMap['jcr:title']}" mode="HTML" />
+                        </label>
+                    </div>
+                </c:forEach>
+            </c:when>
+            <c:otherwise>
+                <select id="${properties.name}" class="form-control" ${properties.multiple ? 'multiple="multiple"' : ''} name="${properties.name}">
+                    <c:if test="${not empty properties.noSelection && !properties.muliple}">
+                        <option>
+                            <sling:encode value="${properties.noSelection}" mode="HTML" />
+                        </option>
+                    </c:if>
+                    <c:forEach var="tag" items="${children}">
+                        <c:set var="selected" value="${false}" />
+                        <c:choose>
+                            <c:when test="${properties.multiple}">
+                                <c:forEach var="val" items="${formData[properties.name]}">
+                                    <c:if test="${val == tag.name}">
+                                        <c:set var="selected" value="${true}" />
+                                    </c:if>
+                                </c:forEach>
+                            </c:when>
+                            <c:when test="${formData[properties.name] == tag.name}">
+                                <c:set var="selected" value="${true}" />
+                            </c:when>
+                        </c:choose>
+                        <option value="${tag.name}" ${selected ? 'selected="selected"' : ''} >
+                            <sling:encode value="${tag.valueMap['jcr:title']}" mode="HTML" />
+                        </option>
+                    </c:forEach>
+                </select>
+            </c:otherwise>
+        </c:choose>
+    </div>
+</c:if>
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/textarea.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/textarea.json
new file mode 100644
index 0000000..d9c86e4
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/textarea.json
@@ -0,0 +1,5 @@
+{
+    "jcr:primaryType" : "sling:Component",
+    "jcr:title": "Textarea",
+    "componentType": "Form Field"
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/textarea/edit.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/textarea/edit.json
new file mode 100644
index 0000000..075bbc7
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/textarea/edit.json
@@ -0,0 +1,68 @@
+ {
+    "jcr:primaryType": "nt:unstructured",
+    "sling:resourceType": "sling-cms/components/editor/slingform",
+    "button": "Save",
+    "fields": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/general/container",
+        "label": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Label",
+            "name": "label"
+        },
+        "name": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Name",
+            "name": "name",
+            "required": true
+        },
+        "value": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Value",
+            "name": "value"
+        },
+        "required": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/select",
+            "label": "Required",
+            "name": "required",
+            "options": {
+                "no": {
+                    "label": "No",
+                    "value": false
+                },
+                "yes": {
+                    "label": "Yes",
+                    "value": true
+                }
+            }
+        },
+        "requiredTypeHint": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+            "name": "required@TypeHint",
+            "value": "Boolean"
+        },
+        "addClasses": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Add Classes",
+            "name": "addClasses"
+        },
+        "additionalAttributes": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/repeating",
+            "label": "Additional Attributes",
+            "name": "additionalAttributes"
+        },
+        "additionalAttributesTypeHint": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+            "name": "additionalAttributes@TypeHint",
+            "value": "String[]"
+        }
+    }
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/textarea/textarea.jsp b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/textarea/textarea.jsp
new file mode 100644
index 0000000..b3c3a61
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/textarea/textarea.jsp
@@ -0,0 +1,44 @@
+<%-- /*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */ --%>
+ <%@include file="/libs/sling-cms/global.jsp"%>
+<div class="${formConfig.fieldGroupClass} ${properties.addClasses}">
+    <c:if test="${not empty properties.label}">
+        <label for="${properties.name}">
+            <sling:encode value="${properties.label}" mode="HTML" />
+            <c:if test="${properties.required}">
+                <span class="${formConfig.fieldRequiredClass}">
+                    *
+                </span>
+            </c:if>
+        </label>
+    </c:if>
+    <c:choose>
+        <c:when test="${not empty formData[properties.name]}">
+            <c:set var="fieldValue" value="${formData[properties.name]}" />
+        </c:when>
+        <c:when test="${not empty properties.value}">
+            <c:set var="fieldValue" value="${properties.value}" />
+        </c:when>
+    </c:choose>
+    <textarea class="${formConfig.fieldClass}" id="${properties.name}" name="${properties.name}" ${properties.required ? 'required="required"' : ''}
+        <c:forEach var="attr" items="${properties.additionalAttributes}">
+            ${fn:split(attr,'\\=')[0]}="${fn:split(attr,'\\=')[1]}"
+        </c:forEach> 
+        >${fieldValue}</textarea>
+</div>
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/textfield.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/textfield.json
new file mode 100644
index 0000000..c2976c7
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/textfield.json
@@ -0,0 +1,5 @@
+{
+    "jcr:primaryType" : "sling:Component",
+    "jcr:title": "Text Field",
+    "componentType": "Form Field"
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/textfield/edit.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/textfield/edit.json
new file mode 100644
index 0000000..30ce24c
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/textfield/edit.json
@@ -0,0 +1,160 @@
+ {
+    "jcr:primaryType": "nt:unstructured",
+    "sling:resourceType": "sling-cms/components/editor/slingform",
+    "button": "Save",
+    "fields": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/general/container",
+        "label": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Label",
+            "name": "label"
+        },
+        "name": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Name",
+            "name": "name",
+            "required": true
+        },
+        "value": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Value",
+            "name": "value"
+        },
+        "placeholder": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Placeholder",
+            "name": "placeholder"
+        },
+        "required": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/select",
+            "label": "Required",
+            "name": "required",
+            "options": {
+                "no": {
+                    "label": "No",
+                    "value": false
+                },
+                "yes": {
+                    "label": "Yes",
+                    "value": true
+                }
+            }
+        },
+        "requiredTypeHint": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+            "name": "required@TypeHint",
+            "value": "Boolean"
+        },
+        "saveAs": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/select",
+            "label": "Save As",
+            "name": "saveAs",
+            "options": {
+                "string": {
+                    "label": "String",
+                    "value": "string"
+                },
+                "date": {
+                    "label": "Date",
+                    "value": "date"
+                },
+                "double": {
+                    "label": "Floating Point (Double)",
+                    "value": "double"
+                },
+                "integer": {
+                    "label": "Integer",
+                    "value": "integer"
+                }
+            }
+        },
+        "type": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/select",
+            "label": "Type",
+            "name": "type",
+            "options": {
+                "text": {
+                    "label": "Text",
+                    "value": "text"
+                },
+                "date": {
+                    "label": "Date",
+                    "value": "date"
+                },
+                "datetime-local": {
+                    "label": "Datetime Local",
+                    "value": "datetime-local"
+                },
+                "email": {
+                    "label": "Email",
+                    "value": "email"
+                },
+                "hidden": {
+                    "label": "Hidden",
+                    "value": "hidden"
+                },
+                "month": {
+                    "label": "Month",
+                    "value": "month"
+                },
+                "number": {
+                    "label": "Number",
+                    "value": "number"
+                },
+                "password": {
+                    "label": "Password",
+                    "value": "password"
+                },
+                "search": {
+                    "label": "Search",
+                    "value": "search"
+                },
+                "tel": {
+                    "label": "Telephone",
+                    "value": "tel"
+                },
+                "time": {
+                    "label": "Time",
+                    "value": "time"
+                },
+                "url": {
+                    "label": "URL",
+                    "value": "url"
+                }
+            }
+        },
+        "pattern": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Pattern",
+            "name": "pattern"
+        },
+        "addClasses": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Add Classes",
+            "name": "addClasses"
+        },
+        "additionalAttributes": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/repeating",
+            "label": "Additional Attributes",
+            "name": "additionalAttributes"
+        },
+        "additionalAttributesTypeHint": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+            "name": "additionalAttributes@TypeHint",
+            "value": "String[]"
+        }
+    }
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/textfield/textfield.jsp b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/textfield/textfield.jsp
new file mode 100644
index 0000000..d5bf932
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fields/textfield/textfield.jsp
@@ -0,0 +1,46 @@
+<%-- /*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */ --%>
+ <%@include file="/libs/sling-cms/global.jsp"%>
+<div class="${formConfig.fieldGroupClass} ${properties.addClasses}">
+    <c:if test="${not empty properties.label}">
+        <label for="${properties.name}">
+            <sling:encode value="${properties.label}" mode="HTML" />
+            <c:if test="${properties.required}">
+                <span class="${formConfig.fieldRequiredClass}">
+                    *
+                </span>
+            </c:if>
+        </label>
+    </c:if>
+    <c:set var="placeholderStr" value="placeholder='${properties.placeholder}'" />
+    <c:set var="patternStr" value="pattern='${properties.pattern}'" />
+    <c:choose>
+        <c:when test="${not empty formData[properties.name]}">
+            <c:set var="fieldValue" value="${formData[properties.name]}" />
+        </c:when>
+        <c:when test="${not empty properties.value}">
+            <c:set var="fieldValue" value="${properties.value}" />
+        </c:when>
+    </c:choose>
+    <input type="${properties.type}" class="${formConfig.fieldClass}" id="${properties.name}" name="${properties.name}" value="${fieldValue}" ${not empty properties.pattern ? patternStr : ''} ${not empty properties.placeholder ? placeholderStr : ''} ${properties.required ? 'required="required"' : ''}
+        <c:forEach var="attr" items="${properties.additionalAttributes}">
+            ${fn:split(attr,'\\=')[0]}="${fn:split(attr,'\\=')[1]}"
+        </c:forEach> 
+        />
+</div>
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/fieldset.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fieldset.json
new file mode 100644
index 0000000..5d40038
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fieldset.json
@@ -0,0 +1,5 @@
+{
+    "jcr:primaryType" : "sling:Component",
+    "jcr:title": "Fieldset",
+    "componentType": "Form Field"
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/fieldset/edit.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fieldset/edit.json
new file mode 100644
index 0000000..3513e17
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fieldset/edit.json
@@ -0,0 +1,15 @@
+ {
+    "jcr:primaryType": "nt:unstructured",
+    "sling:resourceType": "sling-cms/components/editor/slingform",
+    "button": "Save",
+    "fields": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/general/container",
+        "layout": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Legend",
+            "name": "legend"
+        }
+    }
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/fieldset/fieldset.jsp b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fieldset/fieldset.jsp
new file mode 100644
index 0000000..b78d57d
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/fieldset/fieldset.jsp
@@ -0,0 +1,23 @@
+<%-- /*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */ --%>
+ <%@include file="/libs/sling-cms/global.jsp"%>
+<fieldset>
+    <legend><sling:encode value="${properties.legend}" mode="HTML" /></legend>
+    <sling:include path="fields" resourceType="sling-cms/components/general/container" />
+</fieldset>
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/form.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/form.json
new file mode 100644
index 0000000..7bc3031
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/form.json
@@ -0,0 +1,5 @@
+{
+    "jcr:primaryType" : "sling:Component",
+    "jcr:title": "Form",
+    "componentType": "General"
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/form/config.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/form/config.json
new file mode 100644
index 0000000..18de6cb
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/form/config.json
@@ -0,0 +1,81 @@
+ {
+    "jcr:primaryType": "nt:unstructured",
+    "sling:resourceType": "sling-cms/components/general/container",
+    "formClass": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/editor/fields/text",
+        "label": "Form Class",
+        "name": "formClass",
+        "type": "text"
+    },
+    "submitClass": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/editor/fields/text",
+        "label": "Submit Class",
+        "name": "submitClass",
+        "type": "text"
+    },
+    "fieldClass": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/editor/fields/text",
+        "label": "Field Class",
+        "name": "fieldClass",
+        "type": "text"
+    },
+    "actionConfigGroups": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/editor/fields/text",
+        "label": "Action Config Groups",
+        "name": "actionConfigGroups",
+        "type": "text"
+    },
+    "providerConfigGroups": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/editor/fields/text",
+        "label": "Provider Config Groups",
+        "name": "providerConfigGroups",
+        "type": "text"
+    },
+    "fieldConfigGroups": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/editor/fields/text",
+        "label": "Field Config Groups",
+        "name": "fieldConfigGroups",
+        "type": "text"
+    },
+    "fieldGroupClass": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/editor/fields/text",
+        "label": "Field Group Class",
+        "name": "fieldGroupClass",
+        "type": "text"
+    },
+    "fieldRequiredClass": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/editor/fields/text",
+        "label": "Field Required Class",
+        "name": "fieldRequiredClass",
+        "type": "text"
+    },
+    "checkFieldClass": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/editor/fields/text",
+        "label": "Check Field Class",
+        "name": "checkFieldClass",
+        "type": "text"
+    },
+    "checkInputClass": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/editor/fields/text",
+        "label": "Check Input Class",
+        "name": "checkInputClass",
+        "type": "text"
+    },
+    "checkLabelClass": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/editor/fields/text",
+        "label": "Check Label Class",
+        "name": "checkLabelClass",
+        "type": "text"
+    }
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/form/edit.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/form/edit.json
new file mode 100644
index 0000000..d25a3fb
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/form/edit.json
@@ -0,0 +1,40 @@
+ {
+    "jcr:primaryType": "nt:unstructured",
+    "sling:resourceType": "sling-cms/components/editor/slingform",
+    "button": "Save",
+    "fields": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/general/container",
+        "formId": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Form ID",
+            "name": "formId"
+        },
+        "submitText": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "defaultValue": "Submit",
+            "label": "Submit Text",
+            "name": "submitText"
+        },
+        "successMessage": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/richtext",
+            "label": "Success Message",
+            "name": "successMessage"
+        },
+        "actionsErrorMessage": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/richtext",
+            "label": "Actions Error Message",
+            "name": "actionsErrorMessage"
+        },
+        "fieldsErrorMessage": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/richtext",
+            "label": "Fields Error Message",
+            "name": "fieldsErrorMessage"
+        }
+    }
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/form/form.jsp b/reference/src/main/resources/jcr_root/apps/reference/components/forms/form/form.jsp
new file mode 100644
index 0000000..dc55367
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/form/form.jsp
@@ -0,0 +1,47 @@
+<%-- /*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */ --%>
+ <%@include file="/libs/sling-cms/global.jsp"%>
+<c:set var="formConfig" value="${sling:adaptTo(resource,'org.apache.sling.cms.ComponentConfiguration').properties}" scope="request" />
+<c:set var="formData" value="${sling:adaptTo(slingRequest,'org.apache.sling.cms.reference.forms.FormRequest').formData}" scope="request" />
+<form class="${formConfig.formClass}" action="${resource.path}.allowpost.html" method="post" data-analytics-id="${sling:encode(properties.formId,'HTML_ATTR')}">
+    <c:if test="${param.message == 'success'}">
+        ${properties.successMessage}
+    </c:if>
+    <c:if test="${param.error == 'actions'}">
+        ${properties.actionsErrorMessage}
+    </c:if>
+    <c:if test="${param.error == 'fields'}">
+        ${properties.fieldsErrorMessage}
+    </c:if>
+
+    <c:set var="oldAvailableTypes" value="${availableTypes}" />
+
+    <c:set var="availableTypes" value="${formConfig.providerConfigGroups}" scope="request" />
+    <sling:include path="providers" resourceType="sling-cms/components/general/container" />
+    
+    <c:set var="availableTypes" value="${formConfig.fieldConfigGroups}" scope="request" />
+    <sling:include path="fields" resourceType="sling-cms/components/general/container" />
+
+    <c:set var="availableTypes" value="${formConfig.actionConfigGroups}" scope="request" />
+    <sling:include path="actions" resourceType="sling-cms/components/general/container" />
+    <c:set var="availableTypes" value="${oldAvailableTypes}" scope="request" />
+    
+    <button type="submit" class="${formConfig.submitClass}">${properties.submitText}</button>
+    
+</form>
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/userprofile.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/userprofile.json
new file mode 100644
index 0000000..3be98ba
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/userprofile.json
@@ -0,0 +1,5 @@
+{
+    "jcr:primaryType" : "sling:Component",
+    "jcr:title": "User Profile",
+    "componentType": "Form Value Provider"
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/userprofile/edit.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/userprofile/edit.json
new file mode 100644
index 0000000..b004f7f
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/userprofile/edit.json
@@ -0,0 +1,15 @@
+ {
+    "jcr:primaryType": "nt:unstructured",
+    "sling:resourceType": "sling-cms/components/editor/slingform",
+    "button": "Save",
+    "fields": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "sling-cms/components/general/container",
+        "subpath": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Subpath",
+            "name": "subpath"
+        }
+    }
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/userprofile/userprofile.jsp b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/userprofile/userprofile.jsp
new file mode 100644
index 0000000..9905234
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/userprofile/userprofile.jsp
@@ -0,0 +1,26 @@
+<%-- /*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */ --%>
+ <%@include file="/libs/sling-cms/global.jsp"%>
+<c:if test="${cmsEditEnabled == 'true'}">
+    <h3>User Profile</h3>
+    <dl>
+        <dt>Subpath</dt>
+        <dd>${properties.subpath}</dd>
+    </dl>
+</c:if>
\ No newline at end of file
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/form/FormActionResultTest.java b/reference/src/test/java/org/apache/sling/cms/reference/form/FormActionResultTest.java
new file mode 100644
index 0000000..d887a0a
--- /dev/null
+++ b/reference/src/test/java/org/apache/sling/cms/reference/form/FormActionResultTest.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.cms.reference.form;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.sling.cms.reference.forms.FormActionResult;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.junit.Test;
+
+public class FormActionResultTest {
+
+    @Test
+    public void testSuccess() throws FormException {
+        FormActionResult result = FormActionResult.success("Hello World");
+        assertTrue(result.isSucceeded());
+        assertEquals("Hello World", result.getMessage());
+    }
+
+    @Test
+    public void testFailure() throws FormException {
+        FormActionResult result = FormActionResult.failure("Hello World");
+        assertFalse(result.isSucceeded());
+        assertEquals("Hello World", result.getMessage());
+    }
+}
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/form/impl/FormHandlerTest.java b/reference/src/test/java/org/apache/sling/cms/reference/form/impl/FormHandlerTest.java
new file mode 100644
index 0000000..679c674
--- /dev/null
+++ b/reference/src/test/java/org/apache/sling/cms/reference/form/impl/FormHandlerTest.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.cms.reference.form.impl;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+
+import javax.servlet.ServletException;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.apache.sling.cms.reference.forms.FormRequest;
+import org.apache.sling.cms.reference.forms.impl.FormHandler;
+import org.apache.sling.cms.reference.forms.impl.FormRequestImpl;
+import org.apache.sling.cms.reference.forms.impl.actions.SendEmailAction;
+import org.apache.sling.cms.reference.forms.impl.actions.SendEmailActonConfig;
+import org.apache.sling.cms.reference.forms.impl.fields.SelectionHandler;
+import org.apache.sling.cms.reference.forms.impl.fields.TextareaHandler;
+import org.apache.sling.cms.reference.forms.impl.fields.TextfieldHandler;
+import org.apache.sling.event.jobs.JobManager;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.mockito.internal.util.reflection.FieldSetter;
+
+public class FormHandlerTest {
+    @Rule
+    public final SlingContext context = new SlingContext();
+    private FormHandler formHandler;
+
+    @Before
+    public void init() throws NoSuchFieldException, SecurityException, FormException {
+        SlingContextHelper.initContext(context);
+        context.request().setMethod("POST");
+        context.request().setResource(context.resourceResolver().getResource("/form/jcr:content/container/form"));
+
+        context.request().getParameterMap().put("requiredtextarea", new String[] { "Hello World!" });
+        context.request().getParameterMap().put("singleselect", new String[] { "Hello World!" });
+        context.request().getParameterMap().put("anotherkey", new String[] { "Hello World!" });
+        context.request().getParameterMap().put("money", new String[] { "123" });
+        context.request().getParameterMap().put("patternfield", new String[] { "123" });
+        context.request().getParameterMap().put("double", new String[] { "2.7" });
+        context.request().getParameterMap().put("integer", new String[] { "2" });
+        context.request().getParameterMap().put("datefield", new String[] { "2019-02-02" });
+
+        FormRequestImpl formRequest = new FormRequestImpl(context.request());
+
+        FieldSetter.setField(formRequest, formRequest.getClass().getDeclaredField("fieldHandlers"),
+                Arrays.asList(new SelectionHandler(), new TextareaHandler(), new TextfieldHandler()));
+
+        formRequest.init();
+
+        formHandler = new FormHandler() {
+            private static final long serialVersionUID = 1L;
+
+            protected FormRequest getFormRequest(SlingHttpServletRequest request) {
+                return formRequest;
+            }
+        };
+
+        SendEmailAction sendEmailAction = new SendEmailAction();
+        sendEmailAction.activate(new SendEmailActonConfig() {
+
+            @Override
+            public String hostName() {
+                return "smtp.mailtrap.io";
+            }
+
+            @Override
+            public int smtpPort() {
+                return 587;
+            }
+
+            @Override
+            public boolean tlsEnabled() {
+                return true;
+            }
+
+            @Override
+            public String username() {
+                return "e7cfc0e9bb9b80";
+            }
+
+            @Override
+            public String password() {
+                return "b9902898ce236a";
+            }
+
+            @Override
+            public Class<? extends Annotation> annotationType() {
+                return null;
+            }
+
+        });
+        FieldSetter.setField(sendEmailAction, SendEmailAction.class.getDeclaredField("jobManager"),
+                Mockito.mock(JobManager.class));
+
+        FieldSetter.setField(formHandler, FormHandler.class.getDeclaredField("formActions"),
+                Arrays.asList(sendEmailAction));
+        context.request().setMethod("POST");
+    }
+
+    @Test
+    public void testPost() throws ServletException, IOException {
+        formHandler.service(context.request(), context.response());
+    }
+
+}
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/form/impl/FormRequestImplTest.java b/reference/src/test/java/org/apache/sling/cms/reference/form/impl/FormRequestImplTest.java
new file mode 100644
index 0000000..35754b6
--- /dev/null
+++ b/reference/src/test/java/org/apache/sling/cms/reference/form/impl/FormRequestImplTest.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.cms.reference.form.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.apache.sling.cms.reference.forms.impl.FormRequestImpl;
+import org.apache.sling.cms.reference.forms.impl.fields.SelectionHandler;
+import org.apache.sling.cms.reference.forms.impl.fields.TextareaHandler;
+import org.apache.sling.cms.reference.forms.impl.fields.TextfieldHandler;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.internal.util.reflection.FieldSetter;
+
+public class FormRequestImplTest {
+    @Rule
+    public final SlingContext context = new SlingContext();
+    private FormRequestImpl formRequest;
+
+    @Before
+    public void init() throws NoSuchFieldException, SecurityException, FormException {
+        SlingContextHelper.initContext(context);
+        context.request().setResource(context.resourceResolver().getResource("/form/jcr:content/container/form"));
+
+        context.request().getParameterMap().put("requiredtextarea", new String[] { "Hello World!" });
+        context.request().getParameterMap().put("singleselect", new String[] { "Hello World!" });
+        context.request().getParameterMap().put("anotherkey", new String[] { "Hello World!" });
+        context.request().getParameterMap().put("money", new String[] { "123" });
+        context.request().getParameterMap().put("patternfield", new String[] { "123" });
+        context.request().getParameterMap().put("double", new String[] { "2.7" });
+        context.request().getParameterMap().put("integer", new String[] { "2" });
+        context.request().getParameterMap().put("datefield", new String[] { "2019-02-02" });
+
+        formRequest = new FormRequestImpl(context.request());
+
+        FieldSetter.setField(formRequest, formRequest.getClass().getDeclaredField("fieldHandlers"),
+                Arrays.asList(new SelectionHandler(), new TextareaHandler(), new TextfieldHandler()));
+
+        formRequest.init();
+    }
+
+    @Test
+    public void testValueMap() throws FormException {
+        ValueMap formData = formRequest.getFormData();
+        assertNotNull(formData);
+        assertTrue(formData.containsKey("requiredtextarea"));
+        assertFalse(formData.containsKey("textarea"));
+        assertFalse(formData.containsKey("anotherkey"));
+    }
+
+    @Test
+    public void testRequest() throws FormException {
+        assertNotNull(formRequest.getOriginalRequest());
+        assertEquals(context.request(), formRequest.getOriginalRequest());
+    }
+}
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/form/impl/SlingContextHelper.java b/reference/src/test/java/org/apache/sling/cms/reference/form/impl/SlingContextHelper.java
new file mode 100644
index 0000000..749aec5
--- /dev/null
+++ b/reference/src/test/java/org/apache/sling/cms/reference/form/impl/SlingContextHelper.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.cms.reference.form.impl;
+
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+
+public class SlingContextHelper {
+
+    public static final void initContext(SlingContext context) {
+
+        context.load().json("/form.json", "/form");
+
+    }
+}
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/form/impl/actions/SendEmailActionTest.java b/reference/src/test/java/org/apache/sling/cms/reference/form/impl/actions/SendEmailActionTest.java
new file mode 100644
index 0000000..1435533
--- /dev/null
+++ b/reference/src/test/java/org/apache/sling/cms/reference/form/impl/actions/SendEmailActionTest.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.cms.reference.form.impl.actions;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.annotation.Annotation;
+
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.cms.reference.form.impl.SlingContextHelper;
+import org.apache.sling.cms.reference.forms.FormActionResult;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.apache.sling.cms.reference.forms.FormRequest;
+import org.apache.sling.cms.reference.forms.impl.FormRequestImpl;
+import org.apache.sling.cms.reference.forms.impl.actions.SendEmailAction;
+import org.apache.sling.cms.reference.forms.impl.actions.SendEmailActonConfig;
+import org.apache.sling.event.jobs.Job;
+import org.apache.sling.event.jobs.JobManager;
+import org.apache.sling.event.jobs.consumer.JobConsumer.JobResult;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.mockito.internal.util.reflection.FieldSetter;
+
+public class SendEmailActionTest {
+
+    @Rule
+    public final SlingContext context = new SlingContext();
+    private SendEmailAction action;
+    private ResourceResolver resolver;
+
+    @Before
+    public void init() throws NoSuchFieldException, SecurityException {
+        SlingContextHelper.initContext(context);
+        context.request().setResource(context.resourceResolver().getResource("/form/jcr:content/container/form"));
+
+        resolver = context.resourceResolver();
+        action = new SendEmailAction();
+
+        FieldSetter.setField(action, action.getClass().getDeclaredField("jobManager"), Mockito.mock(JobManager.class));
+
+        action.activate(new SendEmailActonConfig() {
+
+            @Override
+            public String hostName() {
+                return "smtp.mailtrap.io";
+            }
+
+            @Override
+            public int smtpPort() {
+                return 587;
+            }
+
+            @Override
+            public boolean tlsEnabled() {
+                return true;
+            }
+
+            @Override
+            public String username() {
+                return "e7cfc0e9bb9b80";
+            }
+
+            @Override
+            public String password() {
+                return "b9902898ce236a";
+            }
+
+            @Override
+            public Class<? extends Annotation> annotationType() {
+                return null;
+            }
+
+        });
+    }
+
+    @Test
+    public void testHandles() {
+
+        assertTrue(action.handles(resolver.getResource("/form/jcr:content/container/form/actions/sendemail")));
+
+        assertFalse(action.handles(resolver.getResource("/form/jcr:content/container/form/actions")));
+
+        assertFalse(action.handles(resolver.getResource("/form/jcr:content/container/form/actions/updateprofile")));
+
+    }
+
+    @Test
+    public void testHandleForm() throws FormException {
+
+        FormRequest formRequest = new FormRequestImpl(context.request());
+        FormActionResult result = action
+                .handleForm(resolver.getResource("/form/jcr:content/container/form/actions/sendemail"), formRequest);
+
+        assertTrue(result.isSucceeded());
+    }
+
+    @Test
+    public void testProcess() throws FormException {
+
+        Job job = Mockito.mock(Job.class);
+        Mockito.when(job.getProperty(SendEmailAction.TO, String.class)).thenReturn("test@user.com");
+        Mockito.when(job.getProperty(SendEmailAction.FROM, String.class)).thenReturn("another@user.com");
+        Mockito.when(job.getProperty(SendEmailAction.SUBJECT, String.class)).thenReturn("SLING CMS");
+        Mockito.when(job.getProperty(SendEmailAction.MESSAGE, String.class)).thenReturn("WOW!");
+
+        JobResult result = action.process(job);
+        assertTrue(result == JobResult.OK);
+    }
+
+}
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/form/impl/fields/SelectionHandlerTest.java b/reference/src/test/java/org/apache/sling/cms/reference/form/impl/fields/SelectionHandlerTest.java
new file mode 100644
index 0000000..47ccf4a
--- /dev/null
+++ b/reference/src/test/java/org/apache/sling/cms/reference/form/impl/fields/SelectionHandlerTest.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.cms.reference.form.impl.fields;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.cms.reference.form.impl.SlingContextHelper;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.apache.sling.cms.reference.forms.impl.fields.SelectionHandler;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class SelectionHandlerTest {
+
+    @Rule
+    public final SlingContext context = new SlingContext();
+    private SelectionHandler handler;
+    private ResourceResolver resolver;
+
+    @Before
+    public void init() {
+        SlingContextHelper.initContext(context);
+        context.request().setResource(context.resourceResolver().getResource("/form/jcr:content/container/form"));
+
+        resolver = context.resourceResolver();
+        handler = new SelectionHandler();
+    }
+
+    @Test
+    public void testHandles() {
+
+        assertTrue(handler
+                .handles(resolver.getResource("/form/jcr:content/container/form/fields/fieldset/fields/singleselect")));
+
+        assertFalse(handler.handles(resolver.getResource("/form/jcr:content/container/form/fields/fieldset/fields")));
+
+        assertTrue(handler
+                .handles(resolver.getResource("/form/jcr:content/container/form/fields/fieldset/fields/multiselect")));
+    }
+
+    @Test
+    public void testSingleSelect() throws FormException {
+        ResourceResolver resolver = context.resourceResolver();
+
+        context.request().getParameterMap().put("singleselect", new String[] { "Hello World" });
+
+        Map<String, Object> formData = new HashMap<>();
+        Resource fieldResource = resolver
+                .getResource("/form/jcr:content/container/form/fields/fieldset/fields/singleselect");
+
+        handler.handleField(context.request(), fieldResource, formData);
+
+        assertEquals("Hello World", formData.get("singleselect"));
+    }
+
+    @Test
+    public void testMissingSingleSelect() throws FormException {
+        ResourceResolver resolver = context.resourceResolver();
+
+        context.request().getParameterMap().put("singleselect", new String[] {});
+
+        Map<String, Object> formData = new HashMap<>();
+        Resource fieldResource = resolver
+                .getResource("/form/jcr:content/container/form/fields/fieldset/fields/singleselect");
+        try {
+            handler.handleField(context.request(), fieldResource, formData);
+            fail();
+        } catch (Exception e) {
+        }
+        assertFalse(formData.containsKey("singleselect"));
+    }
+
+    @Test
+    public void testMultipleSelect() throws FormException {
+        ResourceResolver resolver = context.resourceResolver();
+
+        context.request().getParameterMap().put("multiselect", new String[] {});
+
+        Map<String, Object> formData = new HashMap<>();
+        Resource fieldResource = resolver
+                .getResource("/form/jcr:content/container/form/fields/fieldset/fields/multiselect");
+        handler.handleField(context.request(), fieldResource, formData);
+        assertFalse(formData.containsKey("multiselect"));
+
+        context.request().getParameterMap().put("multiselect", new String[] { "Thing 1", "Thing 2" });
+        handler.handleField(context.request(), fieldResource, formData);
+        assertTrue(formData.containsKey("multiselect"));
+    }
+
+}
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/form/impl/fields/TextareaHandlerTest.java b/reference/src/test/java/org/apache/sling/cms/reference/form/impl/fields/TextareaHandlerTest.java
new file mode 100644
index 0000000..d7e1905
--- /dev/null
+++ b/reference/src/test/java/org/apache/sling/cms/reference/form/impl/fields/TextareaHandlerTest.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.cms.reference.form.impl.fields;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.cms.reference.form.impl.SlingContextHelper;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.apache.sling.cms.reference.forms.impl.fields.TextareaHandler;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class TextareaHandlerTest {
+
+    @Rule
+    public final SlingContext context = new SlingContext();
+    private TextareaHandler handler;
+    private ResourceResolver resolver;
+
+    @Before
+    public void init() {
+        SlingContextHelper.initContext(context);
+        context.request().setResource(context.resourceResolver().getResource("/form/jcr:content/container/form"));
+
+        resolver = context.resourceResolver();
+        handler = new TextareaHandler();
+    }
+
+    @Test
+    public void testHandles() {
+
+        assertTrue(handler
+                .handles(resolver.getResource("/form/jcr:content/container/form/fields/fieldset/fields/textarea")));
+
+        assertFalse(handler.handles(resolver.getResource("/form/jcr:content/container/form/fields/fieldset/fields")));
+
+        assertFalse(handler
+                .handles(resolver.getResource("/form/jcr:content/container/form/fields/fieldset/fields/textfield")));
+    }
+
+    @Test
+    public void testNotRequiredNoValue() throws FormException {
+        ResourceResolver resolver = context.resourceResolver();
+
+        context.request().getParameterMap().put("textarea", new String[] { });
+
+        Map<String, Object> formData = new HashMap<>();
+        Resource fieldResource = resolver
+                .getResource("/form/jcr:content/container/form/fields/fieldset/fields/textarea");
+        handler.handleField(context.request(), fieldResource, formData);
+        assertFalse(formData.containsKey("textarea"));
+    }
+
+    @Test
+    public void testNotRequiredWithValue() throws FormException {
+        ResourceResolver resolver = context.resourceResolver();
+
+        context.request().getParameterMap().put("textarea", new String[] { "Hello World" });
+
+        Map<String, Object> formData = new HashMap<>();
+        Resource fieldResource = resolver
+                .getResource("/form/jcr:content/container/form/fields/fieldset/fields/textarea");
+        handler.handleField(context.request(), fieldResource, formData);
+        assertEquals("Hello World", formData.get("textarea"));
+    }
+    
+
+
+    @Test
+    public void testRequiresNoValue() {
+        ResourceResolver resolver = context.resourceResolver();
+
+        context.request().getParameterMap().put("textarea", new String[0]);
+
+        Map<String, Object> formData = new HashMap<>();
+        Resource fieldResource = resolver
+                .getResource("/form/jcr:content/container/form/fields/fieldset/fields/requiredtextarea");
+        try {
+            handler.handleField(context.request(), fieldResource, formData);
+            fail();
+        } catch (FormException fe) {
+
+        }
+    }
+
+    @Test
+    public void testRequiresWithValue() throws FormException {
+        ResourceResolver resolver = context.resourceResolver();
+
+        context.request().getParameterMap().put("requiredtextarea", new String[] { "Hello World" });
+
+        Map<String, Object> formData = new HashMap<>();
+        Resource fieldResource = resolver
+                .getResource("/form/jcr:content/container/form/fields/fieldset/fields/requiredtextarea");
+        handler.handleField(context.request(), fieldResource, formData);
+        assertEquals("Hello World", formData.get("requiredtextarea"));
+    }
+}
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/form/impl/fields/TextfieldHandlerTest.java b/reference/src/test/java/org/apache/sling/cms/reference/form/impl/fields/TextfieldHandlerTest.java
new file mode 100644
index 0000000..03d3b5c
--- /dev/null
+++ b/reference/src/test/java/org/apache/sling/cms/reference/form/impl/fields/TextfieldHandlerTest.java
@@ -0,0 +1,220 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.sling.cms.reference.form.impl.fields;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.cms.reference.form.impl.SlingContextHelper;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.apache.sling.cms.reference.forms.impl.fields.TextfieldHandler;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class TextfieldHandlerTest {
+
+    @Rule
+    public final SlingContext context = new SlingContext();
+    private TextfieldHandler handler;
+    private ResourceResolver resolver;
+
+    @Before
+    public void init() {
+        SlingContextHelper.initContext(context);
+        context.request().setResource(context.resourceResolver().getResource("/form/jcr:content/container/form"));
+
+        resolver = context.resourceResolver();
+        handler = new TextfieldHandler();
+    }
+
+    @Test
+    public void testDatefield() throws FormException {
+        ResourceResolver resolver = context.resourceResolver();
+
+        context.request().getParameterMap().put("datefield", new String[] { "2019-02-12" });
+
+        Map<String, Object> formData = new HashMap<>();
+        Resource fieldResource = resolver
+                .getResource("/form/jcr:content/container/form/fields/fieldset/fields/datefield");
+        handler.handleField(context.request(), fieldResource, formData);
+        assertTrue(formData.get("datefield") instanceof Calendar);
+
+        context.request().getParameterMap().put("datefield", new String[] { "df-02-12" });
+        try {
+            handler.handleField(context.request(), fieldResource, formData);
+            fail();
+        } catch (FormException pe) {
+
+        }
+
+        Resource invalidDate = resolver
+                .getResource("/form/jcr:content/container/invaliddate");
+        context.request().getParameterMap().put("invalidate", new String[] { "2019-02-12" });
+        try {
+            handler.handleField(context.request(), invalidDate, formData);
+            fail();
+        } catch (FormException pe) {
+
+        }
+
+    }
+
+    @Test
+    public void testDouble() throws FormException {
+        ResourceResolver resolver = context.resourceResolver();
+
+        context.request().getParameterMap().put("double", new String[] { "123.23" });
+
+        Map<String, Object> formData = new HashMap<>();
+        Resource fieldResource = resolver.getResource("/form/jcr:content/container/form/fields/fieldset/fields/double");
+        handler.handleField(context.request(), fieldResource, formData);
+        assertEquals(123.23, formData.get("double"));
+
+        context.request().getParameterMap().put("double", new String[] { "b" });
+        try {
+            handler.handleField(context.request(), fieldResource, formData);
+            fail();
+        } catch (FormException pe) {
+
+        }
+
+    }
+
+    @Test
+    public void testHandles() {
+
+        assertTrue(handler
+                .handles(resolver.getResource("/form/jcr:content/container/form/fields/fieldset/fields/textfield")));
+
+        assertFalse(handler.handles(resolver.getResource("/form/jcr:content/container/form/fields/fieldset/fields")));
+
+        assertFalse(handler
+                .handles(resolver.getResource("/form/jcr:content/container/form/fields/fieldset/fields/textarea")));
+    }
+
+    @Test
+    public void testInteger() throws FormException {
+        ResourceResolver resolver = context.resourceResolver();
+
+        context.request().getParameterMap().put("integer", new String[] { "123" });
+
+        Map<String, Object> formData = new HashMap<>();
+        Resource fieldResource = resolver
+                .getResource("/form/jcr:content/container/form/fields/fieldset/fields/integer");
+        handler.handleField(context.request(), fieldResource, formData);
+        assertEquals(123, formData.get("integer"));
+
+        context.request().getParameterMap().put("integer", new String[] { "b" });
+        try {
+            handler.handleField(context.request(), fieldResource, formData);
+            fail();
+        } catch (FormException pe) {
+
+        }
+
+    }
+
+    @Test
+    public void testNotRequiredNoValue() throws FormException {
+        ResourceResolver resolver = context.resourceResolver();
+
+        context.request().getParameterMap().put("textfield", new String[] {});
+
+        Map<String, Object> formData = new HashMap<>();
+        Resource fieldResource = resolver
+                .getResource("/form/jcr:content/container/form/fields/fieldset/fields/textfield");
+        handler.handleField(context.request(), fieldResource, formData);
+        assertFalse(formData.containsKey("textfield"));
+    }
+
+    @Test
+    public void testNotRequiredWithValue() throws FormException {
+        ResourceResolver resolver = context.resourceResolver();
+
+        context.request().getParameterMap().put("textfield", new String[] { "Hello World" });
+        context.request().getParameterMap().put("textfield2", new String[] { "Hello World" });
+
+        Map<String, Object> formData = new HashMap<>();
+        Resource fieldResource = resolver
+                .getResource("/form/jcr:content/container/form/fields/fieldset/fields/textfield");
+        handler.handleField(context.request(), fieldResource, formData);
+        assertEquals("Hello World", formData.get("textfield"));
+        assertFalse(formData.containsKey("textfield2"));
+    }
+
+    @Test
+    public void testPatternfield() throws FormException {
+        ResourceResolver resolver = context.resourceResolver();
+
+        context.request().getParameterMap().put("patternfield", new String[] { "valasd" });
+
+        Map<String, Object> formData = new HashMap<>();
+        Resource fieldResource = resolver
+                .getResource("/form/jcr:content/container/form/fields/fieldset/fields/patternfield");
+        try {
+            handler.handleField(context.request(), fieldResource, formData);
+            fail();
+        } catch (FormException fe) {
+
+        }
+
+        context.request().getParameterMap().put("patternfield", new String[] { "123" });
+
+        handler.handleField(context.request(), fieldResource, formData);
+        assertEquals("123", formData.get("patternfield"));
+    }
+
+    @Test
+    public void testRequiresNoValue() {
+        ResourceResolver resolver = context.resourceResolver();
+
+        context.request().getParameterMap().put("money", new String[0]);
+
+        Map<String, Object> formData = new HashMap<>();
+        Resource fieldResource = resolver
+                .getResource("/form/jcr:content/container/form/fields/fieldset/fields/requiredtextfield");
+        try {
+            handler.handleField(context.request(), fieldResource, formData);
+            fail();
+        } catch (FormException fe) {
+
+        }
+    }
+
+    @Test
+    public void testRequiresWithValue() throws FormException {
+        ResourceResolver resolver = context.resourceResolver();
+
+        context.request().getParameterMap().put("money", new String[] { "123" });
+
+        Map<String, Object> formData = new HashMap<>();
+        Resource fieldResource = resolver
+                .getResource("/form/jcr:content/container/form/fields/fieldset/fields/requiredtextfield");
+        handler.handleField(context.request(), fieldResource, formData);
+        assertEquals(123, formData.get("money"));
+    }
+}
diff --git a/reference/src/test/resources/form.json b/reference/src/test/resources/form.json
new file mode 100644
index 0000000..c571b1c
--- /dev/null
+++ b/reference/src/test/resources/form.json
@@ -0,0 +1,156 @@
+{
+    "jcr:primaryType": "sling:Page",
+    "jcr:content": {
+        "jcr:primaryType": "nt:unstructured",
+        "jcr:title": "Test Form",
+        "sling:template": "/conf/site/site/templates/form",
+        "sling:resourceType": "test/components/pages/form",
+        "published": true,
+        "container": {
+            "jcr:primaryType": "nt:unstructured",
+            "invaliddate": {
+                "jcr:primaryType": "nt:unstructured",
+                "required": true,
+                "name": "datefield",
+                "type": "text",
+                "label": "Daty",
+                "saveAs": "date",
+                "sling:resourceType": "reference/components/forms/fields/textfield"
+            },
+            "form": {
+                "jcr:primaryType": "nt:unstructured",
+                "submitText": "Submit",
+                "formId": "Test Form",
+                "sling:resourceType": "reference/components/forms/form",
+                "fields": {
+                    "jcr:primaryType": "nt:unstructured",
+                    "fieldset": {
+                        "jcr:primaryType": "nt:unstructured",
+                        "legend": "Section 1",
+                        "sling:resourceType": "reference/components/forms/fieldset",
+                        "fields": {
+                            "jcr:primaryType": "nt:unstructured",
+                            "textarea": {
+                                "jcr:primaryType": "nt:unstructured",
+                                "required": false,
+                                "name": "textarea",
+                                "label": "Textarea",
+                                "sling:resourceType": "reference/components/forms/fields/textarea"
+                            },
+                            "requiredtextarea": {
+                                "jcr:primaryType": "nt:unstructured",
+                                "required": true,
+                                "name": "requiredtextarea",
+                                "label": "Required Textarea",
+                                "sling:resourceType": "reference/components/forms/fields/textarea"
+                            },
+                            "multiselect": {
+                                "jcr:primaryType": "nt:unstructured",
+                                "tagRoot": "/etc/taxonomy/forms/field",
+                                "addClasses": "",
+                                "required": false,
+                                "noSelection": "",
+                                "name": "multiselect",
+                                "label": "MultiSelect",
+                                "multiple": true,
+                                "sling:resourceType": "reference/components/forms/fields/selection",
+                                "display": "radioCheckbox"
+                            },
+                            "textfield": {
+                                "jcr:primaryType": "nt:unstructured",
+                                "addClasses": "",
+                                "required": false,
+                                "name": "textfield",
+                                "type": "tel",
+                                "label": "Money Money Money",
+                                "value": "",
+                                "placeholder": "$",
+                                "sling:resourceType": "reference/components/forms/fields/textfield"
+                            },
+                            "requiredtextfield": {
+                                "jcr:primaryType": "nt:unstructured",
+                                "addClasses": "",
+                                "additionalAttributes": [
+                                    "min=1",
+                                    "max=1000000",
+                                    "step=1"
+                                ],
+                                "pattern": "",
+                                "required": true,
+                                "name": "money",
+                                "type": "number",
+                                "label": "Money Money Money",
+                                "value": "",
+                                "saveAs": "integer",
+                                "placeholder": "$",
+                                "sling:resourceType": "reference/components/forms/fields/textfield"
+                            },
+                            "patternfield": {
+                                "jcr:primaryType": "nt:unstructured",
+                                "required": true,
+                                "name": "patternfield",
+                                "pattern":"\\d+",
+                                "sling:resourceType": "reference/components/forms/fields/textfield"
+                            },
+                            "double": {
+                                "jcr:primaryType": "nt:unstructured",
+                                "required": true,
+                                "name": "double",
+                                "type": "number",
+                                "label": "Daty",
+                                "saveAs": "double",
+                                "sling:resourceType": "reference/components/forms/fields/textfield"
+                            },
+                            "integer": {
+                                "jcr:primaryType": "nt:unstructured",
+                                "required": true,
+                                "name": "integer",
+                                "type": "number",
+                                "label": "Daty",
+                                "saveAs": "integer",
+                                "sling:resourceType": "reference/components/forms/fields/textfield"
+                            },
+                            "datefield": {
+                                "jcr:primaryType": "nt:unstructured",
+                                "required": true,
+                                "name": "datefield",
+                                "type": "date",
+                                "label": "Daty",
+                                "saveAs": "date",
+                                "sling:resourceType": "reference/components/forms/fields/textfield"
+                            },
+                            "singleselect": {
+                                "jcr:primaryType": "nt:unstructured",
+                                "tagRoot": "/etc/taxonomy/forms/period",
+                                "addClasses": "",
+                                "required": true,
+                                "noSelection": "",
+                                "name": "singleselect",
+                                "label": "Single Select",
+                                "multiple": false,
+                                "sling:resourceType": "reference/components/forms/fields/selection",
+                                "display": "select"
+                            }
+                        }
+                    }
+                },
+                "actions": {
+                    "jcr:primaryType": "nt:unstructured",
+                    "sendemail": {
+                        "jcr:primaryType": "nt:unstructured",
+                        "sling:resourceType": "reference/components/forms/actions/sendemail",
+                        "from": "test@email.com",
+                        "to": "test@email.com",
+                        "subject": "A Subject",
+                        "message": "A message"
+                    },
+                    "updateprofile": {
+                        "jcr:primaryType": "nt:unstructured",
+                        "subpath": "jobprofile",
+                        "sling:resourceType": "reference/components/forms/actions/updateprofile"
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file