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 2021/01/25 14:49:52 UTC

[sling-org-apache-sling-app-cms] branch master updated: Forms enhancements (#11)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new b20cd77  Forms enhancements (#11)
b20cd77 is described below

commit b20cd7745e13c68cd3357fae7a10e3e2719979b9
Author: Dan Klco <kl...@users.noreply.github.com>
AuthorDate: Mon Jan 25 09:49:43 2021 -0500

    Forms enhancements (#11)
    
    Fixes SLING-10083 and SLING-10082
    
    * Working on enhanced capabilities for the reference forms
    
    * Adding support for deleting UGC and cleaning up the usage of component configurations
    
    * Improve the handling of the error page
    
    * Updating to add a mapping for the ugc user for the reference project
---
 feature/src/main/features/cms/cms.json             |   3 +-
 reference/pom.xml                                  |  20 +++
 .../sling/cms/reference/forms/FormConstants.java}  |  41 +++--
 .../sling/cms/reference/forms/FormUtils.java}      |  24 +--
 .../cms/reference/forms/impl/FormHandler.java      |  93 +++++++----
 .../cms/reference/forms/impl/FormRequestImpl.java  |  27 +++-
 .../forms/impl/actions/CreateUserAction.java       | 150 ++++++++++++++++++
 .../actions/DeleteUserGeneratedContentAction.java  | 103 ++++++++++++
 .../impl/actions/RequestPasswordResetAction.java   | 101 ++++++++++++
 .../forms/impl/actions/ResetPasswordAction.java    | 110 +++++++++++++
 .../forms/impl/actions/SendEmailAction.java        |  18 ++-
 .../forms/impl/actions/UpdateProfileAction.java    |  90 +++++------
 .../actions/UpdateUserGeneratedContentAction.java  | 104 +++++++++++++
 .../impl/actions/UserGeneratedContentAction.java   |  18 ++-
 .../forms/impl/fields/SelectionHandler.java        |  27 +++-
 .../forms/impl/fields/TextareaHandler.java         |  27 +++-
 .../forms/impl/fields/TextfieldHandler.java        |  26 +++-
 .../providers/RequestParametersValueProvider.java  |  56 +++++++
 .../providers/SuffixResourceFormValueProvider.java |  63 ++++++++
 .../providers/UserProfileFormValueProvider.java    |   3 +-
 .../main/resources/OSGI-INF/l10n/bundle.properties |  20 ++-
 .../components/forms/actions/createuser.json       |   5 +
 .../forms/actions/createuser/createuser.jsp        |  32 ++++
 .../components/forms/actions/createuser/edit.json  |  47 ++++++
 .../components/forms/actions/deleteugc.json        |   5 +
 .../forms/actions/deleteugc/deleteugc.jsp          |  26 ++++
 .../components/forms/actions/deleteugc/edit.json   |  14 ++
 .../forms/actions/requestpasswordreset/edit.json   |  16 ++
 .../requestpasswordreset/requestpasswordreset.jsp  |  26 ++++
 .../components/forms/actions/resetpassword.json    |   5 +
 .../forms/actions/resetpassword/edit.json          |   5 +
 .../forms/actions/resetpassword/resetpassword.jsp  |  22 +++
 .../components/forms/actions/updateugc.json        |   5 +
 .../components/forms/actions/updateugc/edit.json   |  14 ++
 .../forms/actions/updateugc/updateugc.jsp          |  26 ++++
 .../components/forms/fields/textarea/textarea.jsp  |   4 +-
 .../forms/fields/textfield/textfield.jsp           |   2 +-
 .../apps/reference/components/forms/form/edit.json |   6 +
 .../apps/reference/components/forms/login.json     |   5 +
 .../reference/components/forms/login/edit.json     |  46 ++++++
 .../reference/components/forms/login/login.jsp     |  42 +++++
 .../forms/providers/requestparameters.json         |   6 +
 .../forms/providers/requestparameters/edit.json    |  21 +++
 .../requestparameters/requestparameters.jsp        |  26 ++++
 .../components/forms/providers/suffixresource.json |   6 +
 .../forms/providers/suffixresource/edit.json       |  28 ++++
 .../providers/suffixresource/suffixresource.jsp    |  28 ++++
 .../cms/reference/forms/FormActionResultTest.java  |   2 -
 .../cms/reference/forms/impl/FormHandlerTest.java  |  55 +++++--
 .../reference/forms/impl/FormRequestImplTest.java  |  42 ++++-
 .../forms/impl/actions/CreateUserActionTest.java   | 153 ++++++++++++++++++
 .../DeleteUserGeneratedContentActionTest.java      | 153 ++++++++++++++++++
 .../actions/RequestPasswordResetActionTest.java    | 134 ++++++++++++++++
 .../impl/actions/ResetPasswordActionTest.java      | 168 ++++++++++++++++++++
 .../forms/impl/actions/SendEmailActionTest.java    |   5 +-
 .../impl/actions/UpdateProfileActionTest.java      | 165 ++++++++++++++++++++
 .../UpdateUserGeneratedContentActionTest.java      | 172 +++++++++++++++++++++
 .../actions/UserGeneratedContentActionTest.java    |  90 +++++++++++
 ...onHandlerTest.java => HoneypotHandlerTest.java} |  55 ++-----
 .../forms/impl/fields/SelectionHandlerTest.java    |  17 +-
 .../forms/impl/fields/TextareaHandlerTest.java     |  20 ++-
 .../forms/impl/fields/TextfieldHandlerTest.java    |  63 +++++++-
 .../RequestParametersValueProviderTest.java        |  76 +++++++++
 .../SuffixResourceFormValueProviderTest.java       | 156 +++++++++++++++++++
 reference/src/test/resources/form.json             |  41 ++++-
 transformer/src/test/resources/thumbnail.png       | Bin 21183 -> 20437 bytes
 66 files changed, 2939 insertions(+), 220 deletions(-)

diff --git a/feature/src/main/features/cms/cms.json b/feature/src/main/features/cms/cms.json
index 5644ea1..6013361 100644
--- a/feature/src/main/features/cms/cms.json
+++ b/feature/src/main/features/cms/cms.json
@@ -190,7 +190,8 @@
         },
         "org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended~sling-cms-ugc": {
             "user.mapping": [
-                "org.apache.sling.cms.core:sling-cms-ugc=sling-cms-ugc"
+                "org.apache.sling.cms.core:sling-cms-ugc=sling-cms-ugc",
+                "org.apache.sling.cms.reference:sling-cms-ugc=sling-cms-ugc"
             ]
         },
         "org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended~sling-cms-versionmgr": {
diff --git a/reference/pom.xml b/reference/pom.xml
index 6c4b682..12e59ac 100644
--- a/reference/pom.xml
+++ b/reference/pom.xml
@@ -41,6 +41,26 @@
                 <groupId>biz.aQute.bnd</groupId>
                 <artifactId>bnd-maven-plugin</artifactId>
             </plugin>
+            <plugin>
+                <groupId>org.jacoco</groupId>
+                <artifactId>jacoco-maven-plugin</artifactId>
+                <version>0.8.2</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>prepare-agent</goal>
+                        </goals>
+                    </execution>
+                    <!-- attached to Maven test phase -->
+                    <execution>
+                        <id>report</id>
+                        <phase>test</phase>
+                        <goals>
+                            <goal>report</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
         </plugins>
     </build>
 
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/FormActionResultTest.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/FormConstants.java
similarity index 51%
copy from reference/src/test/java/org/apache/sling/cms/reference/forms/FormActionResultTest.java
copy to reference/src/main/java/org/apache/sling/cms/reference/forms/FormConstants.java
index 9ed86d7..ea556ae 100644
--- a/reference/src/test/java/org/apache/sling/cms/reference/forms/FormActionResultTest.java
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/FormConstants.java
@@ -16,27 +16,22 @@
  */
 package org.apache.sling.cms.reference.forms;
 
-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());
-    }
+public class FormConstants {
+
+    public static final String PATH_PROFILE = "profile";
+
+    public static final String PN_EMAIL = "email";
+
+    public static final String PN_SERVICE_USER = "serviceUser";
+
+    public static final String PN_ALLOWED_PROPERTIES = "allowedProperties";
+
+    public static final String PN_SUBPATH = "subpath";
+
+    public static final String SERVICE_USER = "slingcms-reference-usermanager";
+
+    public static final String PN_RESETTOKEN = "resettoken";
+    public static final String PN_RESETTIMEOUT = "resettimeout";
+
+    public static final String PN_PASSWORD = "password";
 }
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/FormActionResultTest.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/FormUtils.java
similarity index 51%
copy from reference/src/test/java/org/apache/sling/cms/reference/forms/FormActionResultTest.java
copy to reference/src/main/java/org/apache/sling/cms/reference/forms/FormUtils.java
index 9ed86d7..3e77430 100644
--- a/reference/src/test/java/org/apache/sling/cms/reference/forms/FormActionResultTest.java
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/FormUtils.java
@@ -16,27 +16,13 @@
  */
 package org.apache.sling.cms.reference.forms;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import java.util.stream.Stream;
 
-import org.apache.sling.cms.reference.forms.FormActionResult;
-import org.apache.sling.cms.reference.forms.FormException;
-import org.junit.Test;
+import org.apache.sling.api.resource.Resource;
 
-public class FormActionResultTest {
+public class FormUtils {
 
-    @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());
+    public static final boolean handles(String[] supportedTypes, Resource resource) {
+        return Stream.of(supportedTypes).anyMatch(t -> t.equals(resource.getResourceType()));
     }
 }
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
index b75ad13..66feb4f 100644
--- 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
@@ -30,12 +30,14 @@ 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.resource.ValueMap;
 import org.apache.sling.api.servlets.SlingAllMethodsServlet;
 import org.apache.sling.api.wrappers.SlingHttpServletRequestWrapper;
 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.FormActionResult;
 import org.apache.sling.cms.reference.forms.FormException;
 import org.apache.sling.cms.reference.forms.FormRequest;
 import org.osgi.service.component.annotations.Activate;
@@ -63,37 +65,38 @@ public class FormHandler extends SlingAllMethodsServlet {
     @Override
     protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response)
             throws ServletException, IOException {
+        ValueMap properties = request.getResource().getValueMap();
 
         String pagePath = Optional.ofNullable(request.getResource().adaptTo(PageManager.class))
                 .map(PageManager::getPage).map(Page::getPath)
                 .orElse(StringUtils.substringBefore(request.getResource().getPath(), "/" + JcrConstants.JCR_CONTENT));
+        String successPage = null;
+        String errorPage = pagePath;
 
         StringSubstitutor sub = null;
         try {
-            if (request.getResource().getChild("actions") == null) {
-                throw new FormException("No actions provided to handle this form submission");
-            }
-            List<Resource> actionResources = ResourceTree.stream(request.getResource().getChild("actions"))
-                    .map(ResourceTree::getResource).collect(Collectors.toList());
-
-            FormRequest formRequest = getFormRequest(request);
+            log.debug("Extracting form request...");
+            FormRequest formRequest = request.adaptTo(FormRequest.class);
             if (formRequest == null) {
                 log.warn("Unable to create form request");
-                response.sendRedirect(request.getResourceResolver().map(request, pagePath) + ".html?error=fields");
+                response.sendRedirect(this.resolveUrl(request, errorPage, "error=init"));
                 return;
             }
+
+            log.debug("Loading fields...");
+            boolean fieldsLoadSucceeded = ((FormRequestImpl) formRequest).initFields();
             sub = new StringSubstitutor(formRequest.getFormData());
-            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;
-                    }
-                }
+            successPage = sub.replace(properties.get("successPage", pagePath));
+            errorPage = sub.replace(properties.get("errorPage", pagePath));
+            if (!fieldsLoadSucceeded) {
+                log.warn("Field initialization failed, check logs");
+                response.sendRedirect(this.resolveUrl(request, errorPage, "error=fields"));
+                return;
             }
+            request.getSession().setAttribute(formRequest.getSessionId(), formRequest.getFormData());
+
+            log.debug("Calling actions...");
+            callActions(request, formRequest);
             request.getSession().removeAttribute(formRequest.getSessionId());
         } catch (FormException e) {
             log.warn("Exception executing actions", e);
@@ -101,31 +104,61 @@ public class FormHandler extends SlingAllMethodsServlet {
             return;
         }
 
-        String thankYouPage = sub.replace(request.getResource().getValueMap().get("successPage", pagePath));
-        if (StringUtils.isNotBlank(thankYouPage)) {
-            if ("forward".equals(request.getResource().getValueMap().get("successAction", String.class))) {
+        if (StringUtils.isNotBlank(successPage)) {
+            if ("forward".equals(properties.get("successAction", String.class))) {
                 SlingHttpServletRequestWrapper requestWrapper = new SlingHttpServletRequestWrapper(request) {
                     @Override
                     public String getMethod() {
                         return "GET";
                     }
                 };
-
-                request.getRequestDispatcher(thankYouPage).forward(requestWrapper, response);
+                request.getRequestDispatcher(successPage).forward(requestWrapper, response);
             } else {
-                response.sendRedirect(resolveThankYouPage(request, thankYouPage));
+                response.sendRedirect(resolveUrl(request, successPage, "message=success"));
             }
         } else {
-            response.sendRedirect(resolveThankYouPage(request, thankYouPage));
+            response.sendRedirect(resolveUrl(request, successPage, "message=success"));
         }
     }
 
-    private String resolveThankYouPage(SlingHttpServletRequest request, String thankYouPage) {
-        if (!thankYouPage.contains(".html")) {
-            thankYouPage += ".html";
+    private void callActions(SlingHttpServletRequest request, FormRequest formRequest) throws FormException {
+        Resource actions = request.getResource().getChild("actions");
+        if (actions == null) {
+            throw new FormException("No actions provided to handle this form submission");
+        }
+        List<Resource> actionResources = ResourceTree.stream(actions).map(ResourceTree::getResource)
+                .collect(Collectors.toList());
+
+        for (Resource actionResource : actionResources) {
+            log.debug("Finding action handler for: {}", actionResource);
+            FormAction action = formActions.stream().filter(fa -> fa.handles(actionResource)).findFirst().orElse(null);
+            if (action != null) {
+                FormActionResult result = action.handleForm(actionResource, formRequest);
+                if (!result.isSucceeded()) {
+                    throw new FormException(
+                            "Failed to invoke action: " + action + " with message: " + result.getMessage());
+                } else {
+                    log.debug("Successfully invoked action: {}", result.getMessage());
+                }
+            }
+        }
+    }
+
+    private String resolveUrl(SlingHttpServletRequest request, String url, String qs) {
+        if (url.contains("?")) {
+            qs = "&" + qs;
+        } else {
+            qs = "?" + qs;
+        }
+        if (url.startsWith("/")) {
+            if (!url.contains(".html")) {
+                url += ".html";
+            }
+            url += qs;
+            return request.getResourceResolver().map(request, url);
+        } else {
+            return url + qs;
         }
-        thankYouPage += "?message=success";
-        return request.getResourceResolver().map(request, thankYouPage);
     }
 
     protected FormRequest getFormRequest(SlingHttpServletRequest request) throws FormException {
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
index 396d388..2ab4b7d 100644
--- 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
@@ -101,19 +101,32 @@ public class FormRequestImpl implements FormRequest {
         return request;
     }
 
-    public void initFields() throws FormException {
+    public boolean initFields() {
         List<Resource> fields = ResourceTree.stream(getFormResource().getChild("fields")).map(ResourceTree::getResource)
                 .collect(Collectors.toList());
+        boolean successful = true;
         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;
+            formData.remove(getErrorKey(field));
+            try {
+                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;
+                    }
                 }
+            } catch (FormException fe) {
+                log.warn("Failed to populate field {} due to exception", field, fe);
+                successful = false;
+                formData.put(getErrorKey(field), fe.getMessage());
             }
         }
+        return successful;
+    }
+
+    private String getErrorKey(Resource field) {
+        return "fielderror-" + field.getValueMap().get("name", String.class);
     }
 
     @Override
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/CreateUserAction.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/CreateUserAction.java
new file mode 100644
index 0000000..43bbd18
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/CreateUserAction.java
@@ -0,0 +1,150 @@
+/*
+ * 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.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.text.StringSubstitutor;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalImpl;
+import org.apache.sling.api.resource.LoginException;
+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.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.resource.ResourceUtil;
+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.FormConstants;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.apache.sling.cms.reference.forms.FormRequest;
+import org.jetbrains.annotations.NotNull;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(service = { FormAction.class })
+public class CreateUserAction implements FormAction {
+
+    private static final Logger log = LoggerFactory.getLogger(CreateUserAction.class);
+    public static final String RESOURCE_TYPE = "reference/components/forms/actions/createuser";
+    public static final String PROFILE_PROPERTIES = "profileProperties";
+    public static final String GROUPS = "groups";
+    public static final String PN_USERNAME = "username";
+    public static final String PN_INTERMEDIATE_PATH = "intermediatePath";
+
+    private final ResourceResolverFactory factory;
+
+    @Activate
+    public CreateUserAction(@Reference ResourceResolverFactory factory) {
+        this.factory = factory;
+    }
+
+    @Override
+    public FormActionResult handleForm(final Resource actionResource, final FormRequest request) throws FormException {
+        final StringSubstitutor sub = new StringSubstitutor(request.getFormData());
+
+        final ValueMap properties = actionResource.getValueMap();
+
+        String username = request.getFormData().get(PN_USERNAME, String.class);
+        String password = request.getFormData().get(FormConstants.PN_PASSWORD, String.class);
+
+        String intermediatePath = properties.get(PN_INTERMEDIATE_PATH, String.class);
+
+        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
+            return FormActionResult.failure("Empty username / password");
+        }
+
+        try {
+            try (ResourceResolver adminResolver = factory.getServiceResourceResolver(
+                    Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, FormConstants.SERVICE_USER))) {
+                JackrabbitSession session = (JackrabbitSession) adminResolver.adaptTo(Session.class);
+                final UserManager userManager = session.getUserManager();
+
+                if (userManager.getAuthorizable(username) == null) {
+
+                    log.debug("Creating user {}", username);
+                    User user = userManager.createUser(username, password, new PrincipalImpl(username),
+                            intermediatePath);
+
+                    String[] groups = properties.get(GROUPS, new String[0]);
+                    for (String g : groups) {
+                        String groupName = sub.replace(g);
+                        Authorizable group = userManager.getAuthorizable(groupName);
+                        if (group == null || !group.isGroup()) {
+                            log.error("Could not find group {}", groupName);
+                            return FormActionResult.failure("Could not find group: " + groupName);
+                        } else {
+                            ((Group) group).addMember(user);
+                        }
+
+                    }
+                    log.debug("Updating profile for {}", username);
+                    updateProfile(adminResolver, user, properties.get(PROFILE_PROPERTIES, new String[0]),
+                            request.getFormData());
+
+                    log.debug("Saving changes!");
+                    adminResolver.commit();
+                    return FormActionResult.success("User " + username + " created successfully");
+                } else {
+                    log.error("Failed to create user, {} already exists", username);
+                    return FormActionResult.failure("User " + username + " already exists");
+                }
+            }
+        } catch (LoginException le) {
+            log.error("Failed to get user manager service user", le);
+            return FormActionResult.failure("Failed to get service user");
+        } catch (PersistenceException | RepositoryException e) {
+            log.error("Failed to create user " + username, e);
+            return FormActionResult.failure("Failed to create user " + username);
+        }
+    }
+
+    private void updateProfile(ResourceResolver adminResolver, User user, String @NotNull [] toset, ValueMap formData)
+            throws PersistenceException, RepositoryException {
+        if (toset.length > 0) {
+            Map<String, Object> properties = new HashMap<>();
+            Arrays.stream(toset).filter(k -> formData.keySet().contains(k))
+                    .forEach(k -> properties.put(k, formData.get(k)));
+            properties.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+            ResourceUtil.getOrCreateResource(adminResolver, user.getPath() + "/profile", properties,
+                    JcrConstants.NT_UNSTRUCTURED, false);
+        }
+
+    }
+
+    @Override
+    public boolean handles(Resource actionResource) {
+        return RESOURCE_TYPE.equals(actionResource.getResourceType());
+    }
+
+}
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/DeleteUserGeneratedContentAction.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/DeleteUserGeneratedContentAction.java
new file mode 100644
index 0000000..8f9f6b0
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/DeleteUserGeneratedContentAction.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.actions;
+
+import java.util.Collections;
+
+import org.apache.commons.text.StringSubstitutor;
+import org.apache.sling.api.resource.LoginException;
+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.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.cms.CMSConstants;
+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.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(service = FormAction.class, immediate = true)
+public class DeleteUserGeneratedContentAction implements FormAction {
+
+    private static final Logger log = LoggerFactory.getLogger(DeleteUserGeneratedContentAction.class);
+
+    private ResourceResolverFactory factory;
+
+    @Activate
+    public DeleteUserGeneratedContentAction(@Reference ResourceResolverFactory factory) {
+        this.factory = factory;
+    }
+
+    @Override
+    public FormActionResult handleForm(Resource actionResource, FormRequest request) throws FormException {
+        log.trace("handleForm");
+
+        ValueMap properties = actionResource.getValueMap();
+
+        try (ResourceResolver resolver = getResourceResolver()) {
+            StringSubstitutor sub = new StringSubstitutor(request.getFormData());
+
+            String path = sub.replace(properties.get("path", String.class));
+            log.debug("Deleting UGC resource at path: {}", path);
+
+            Resource resource = resolver.getResource(path);
+            if (resource == null) {
+                throw new FormException("Could not find UGC resource at path: " + path);
+            }
+            Resource ugcParent = findUgcParent(resource);
+
+            if (!request.getOriginalRequest().getResourceResolver().getUserID()
+                    .equals(ugcParent.getValueMap().get("user", String.class))) {
+                throw new FormException("Cannot delete content not created by the current user");
+            }
+
+            resolver.delete(ugcParent);
+            resolver.commit();
+            log.debug("Successfully deleted user generated content");
+            return FormActionResult.success("Deleted user generated content");
+        } catch (LoginException | PersistenceException e) {
+            throw new FormException("Failed to delete user generated content", e);
+        }
+    }
+
+    private Resource findUgcParent(Resource resource) throws FormException {
+        if (CMSConstants.NT_UGC.equals(resource.getResourceType())) {
+            return resource;
+        } else if (resource.getParent() != null) {
+            return findUgcParent(resource.getParent());
+        } else {
+            throw new FormException("Failed to find UGC Parent");
+        }
+    }
+
+    private ResourceResolver getResourceResolver() throws LoginException {
+        return factory.getServiceResourceResolver(
+                Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, "sling-cms-ugc"));
+    }
+
+    @Override
+    public boolean handles(Resource actionResource) {
+        return "reference/components/forms/actions/deleteugc".equals(actionResource.getResourceType());
+    }
+
+}
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/RequestPasswordResetAction.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/RequestPasswordResetAction.java
new file mode 100644
index 0000000..3e95ed9
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/RequestPasswordResetAction.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.Calendar;
+import java.util.Collections;
+import java.util.UUID;
+
+import javax.jcr.Session;
+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.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.cms.reference.forms.FormAction;
+import org.apache.sling.cms.reference.forms.FormActionResult;
+import org.apache.sling.cms.reference.forms.FormConstants;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.apache.sling.cms.reference.forms.FormRequest;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(service = { FormAction.class })
+public class RequestPasswordResetAction implements FormAction {
+
+    public static final String RESOURCE_TYPE = "reference/components/forms/actions/requestpasswordreset";
+    public static final String PN_RESETTOKEN = "resettoken";
+    public static final String PN_RESETTIMEOUT = "resettimeout";
+    private static final Logger log = LoggerFactory.getLogger(RequestPasswordResetAction.class);
+    private ResourceResolverFactory factory;
+
+    @Activate
+    public RequestPasswordResetAction(@Reference ResourceResolverFactory factory) {
+        this.factory = factory;
+
+    }
+
+    @Override
+    public FormActionResult handleForm(Resource actionResource, FormRequest request) throws FormException {
+        String email = request.getFormData().get(FormConstants.PN_EMAIL, String.class);
+        int resetTimeout = actionResource.getValueMap().get(PN_RESETTIMEOUT, Integer.class);
+
+        try (ResourceResolver adminResolver = factory.getServiceResourceResolver(
+                Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, FormConstants.SERVICE_USER))) {
+
+            JackrabbitSession session = (JackrabbitSession) adminResolver.adaptTo(Session.class);
+            final UserManager userManager = session.getUserManager();
+
+            if (userManager.getAuthorizable(email) != null) {
+
+                User user = (User) userManager.getAuthorizable(email);
+
+                String resetToken = UUID.randomUUID().toString();
+                Calendar deadline = Calendar.getInstance();
+                deadline.add(Calendar.SECOND, resetTimeout);
+
+                ValueFactory vf = session.getValueFactory();
+
+                user.setProperty(PN_RESETTOKEN, vf.createValue(resetToken));
+                user.setProperty(PN_RESETTIMEOUT, vf.createValue(deadline));
+
+                request.getFormData().put(PN_RESETTOKEN, resetToken);
+
+                adminResolver.commit();
+
+            } else {
+                log.warn("Unable to find user {}", email);
+                return FormActionResult.failure("Unable to find user");
+            }
+        } catch (Exception e) {
+            throw new FormException("Failed to initiate password reset", e);
+        }
+        return FormActionResult.success("Reset token created");
+    }
+
+    @Override
+    public boolean handles(Resource actionResource) {
+        return RESOURCE_TYPE.equals(actionResource.getResourceType());
+    }
+
+}
\ No newline at end of file
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/ResetPasswordAction.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/ResetPasswordAction.java
new file mode 100644
index 0000000..56d8d1d
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/ResetPasswordAction.java
@@ -0,0 +1,110 @@
+/*
+ * 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.Calendar;
+import java.util.Collections;
+
+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.api.resource.ResourceResolverFactory;
+import org.apache.sling.cms.reference.forms.FormAction;
+import org.apache.sling.cms.reference.forms.FormActionResult;
+import org.apache.sling.cms.reference.forms.FormConstants;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.apache.sling.cms.reference.forms.FormRequest;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(service = { FormAction.class })
+public class ResetPasswordAction implements FormAction {
+
+    public static final String RESOURCE_TYPE = "reference/components/forms/actions/resetpassword";
+    private static final Logger log = LoggerFactory.getLogger(ResetPasswordAction.class);
+    private ResourceResolverFactory factory;
+
+    @Activate
+    public ResetPasswordAction(@Reference ResourceResolverFactory factory) {
+        this.factory = factory;
+    }
+
+    @Override
+    public FormActionResult handleForm(Resource actionResource, FormRequest request) throws FormException {
+        String email = request.getFormData().get(FormConstants.PN_EMAIL, String.class);
+        String resetToken = request.getFormData().get(FormConstants.PN_RESETTOKEN, String.class);
+        String password = request.getFormData().get("password", String.class);
+
+        try (ResourceResolver adminResolver = factory.getServiceResourceResolver(
+                Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, FormConstants.SERVICE_USER))) {
+
+            JackrabbitSession session = (JackrabbitSession) adminResolver.adaptTo(Session.class);
+            final UserManager userManager = session.getUserManager();
+
+            User user = (User) userManager.getAuthorizable(email);
+
+            if (user == null) {
+                return FormActionResult.failure("No user found for " + email);
+            }
+
+            String storedToken = getValue(user.getProperty(FormConstants.PN_RESETTOKEN), String.class);
+            Calendar resetTimeout = getValue(user.getProperty(FormConstants.PN_RESETTIMEOUT), Calendar.class);
+            if (storedToken == null || !storedToken.equals(resetToken)) {
+                return FormActionResult.failure("Failed to validate token");
+            }
+            if (Calendar.getInstance().after(resetTimeout)) {
+                return FormActionResult.failure("Timeout already passed");
+            }
+            user.changePassword(password);
+
+            log.debug("Saving changes!");
+            adminResolver.commit();
+
+            return FormActionResult.success("Password reset successfully!");
+        } catch (Exception e) {
+            throw new FormException("Failed to complete password reset", e);
+        }
+    }
+
+    private <E> E getValue(Value[] property, Class<E> clazz) throws IllegalStateException, RepositoryException {
+        if (property != null && property.length > 0) {
+            Value v = property[0];
+            if (clazz.isAssignableFrom(String.class)) {
+                return clazz.cast(v.getString());
+            }
+            if (clazz.isAssignableFrom(Calendar.class)) {
+                return clazz.cast(v.getDate());
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public boolean handles(Resource actionResource) {
+        return RESOURCE_TYPE.equals(actionResource.getResourceType());
+    }
+
+}
\ No newline at end of file
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
index 630e129..3a40c11 100644
--- 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
@@ -28,6 +28,7 @@ import org.apache.sling.cms.reference.forms.FormException;
 import org.apache.sling.cms.reference.forms.FormRequest;
 import org.apache.sling.commons.messaging.mail.MailService;
 import org.jetbrains.annotations.NotNull;
+import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
 import org.slf4j.Logger;
@@ -36,6 +37,7 @@ import org.slf4j.LoggerFactory;
 @Component(service = { FormAction.class })
 public class SendEmailAction implements FormAction {
 
+    public static final String RESOURCE_TYPE = "reference/components/forms/actions/sendemail";
     public static final String FROM = "from";
     private static final Logger log = LoggerFactory.getLogger(SendEmailAction.class);
 
@@ -43,7 +45,12 @@ public class SendEmailAction implements FormAction {
     public static final String SUBJECT = "subject";
     public static final String TO = "to";
 
-    private MailService mailService;
+    private final MailService mailService;
+
+    @Activate
+    public SendEmailAction(@Reference MailService mailService) {
+        this.mailService = mailService;
+    }
 
     @Override
     public FormActionResult handleForm(final Resource actionResource, final FormRequest request) throws FormException {
@@ -71,13 +78,8 @@ public class SendEmailAction implements FormAction {
     }
 
     @Override
-    public boolean handles(final Resource actionResource) {
-        return "reference/components/forms/actions/sendemail".equals(actionResource.getResourceType());
-    }
-
-    @Reference
-    public void setMailService(MailService mailService) {
-        this.mailService = mailService;
+    public boolean handles(Resource actionResource) {
+        return RESOURCE_TYPE.equals(actionResource.getResourceType());
     }
 
 }
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
index 53d24d9..84dea5f 100644
--- 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
@@ -19,6 +19,7 @@ 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.Optional;
 import java.util.stream.Collectors;
 
 import javax.jcr.RepositoryException;
@@ -34,6 +35,7 @@ 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.FormConstants;
 import org.apache.sling.cms.reference.forms.FormException;
 import org.apache.sling.cms.reference.forms.FormRequest;
 import org.osgi.service.component.annotations.Component;
@@ -43,64 +45,66 @@ import org.slf4j.LoggerFactory;
 @Component(service = FormAction.class)
 public class UpdateProfileAction implements FormAction {
 
+    public static final String RESOURCE_TYPE = "reference/components/forms/actions/updateprofile";
+
     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);
-
-        if (session != null) {
-            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");
+        try {
+            JackrabbitSession session = Optional.ofNullable((JackrabbitSession) resolver.adaptTo(Session.class))
+                    .orElseThrow(() -> new RepositoryException("Unable to get Jackrabbit Session"));
+
+            final UserManager userManager = session.getUserManager();
+            if (userManager.getAuthorizable(userId) == null) {
+
+                log.warn("No profile found for {}", userId);
+                return FormActionResult.failure("No profile found for " + userId);
+            }
+
+            User user = (User) userManager.getAuthorizable(userId);
+            log.debug("Updating profile for {}", userId);
+
+            String subpath = actionResource.getValueMap().get(FormConstants.PN_SUBPATH, FormConstants.PATH_PROFILE);
+            ValueFactory valueFactory = session.getValueFactory();
+
+            for (Entry<String, Object> e : request.getFormData().entrySet()) {
+                Value value = null;
+                if (e.getValue() instanceof String[]) {
+                    Value[] values = Arrays.stream(((String[]) e.getValue())).map(valueFactory::createValue)
+                            .collect(Collectors.toList()).toArray(new Value[0]);
+                    user.setProperty(subpath + "/" + e.getKey(), values);
                 } else {
-                    log.warn("No profile found for {}", userId);
-                    return FormActionResult.failure("No profile found for " + userId);
+                    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((Integer) e.getValue());
+                    } else {
+                        value = valueFactory.createValue((String) e.getValue());
+                    }
+                    user.setProperty(subpath + "/" + e.getKey(), value);
                 }
-            } catch (RepositoryException | PersistenceException e) {
-                log.warn("Failed to update profile for {}", userId, e);
-                return FormActionResult.failure("Failed to update profile for " + userId);
             }
-        } else {
-            log.warn("Failed to get session for {}", userId);
-            return FormActionResult.failure("Failed to get session for " + userId);
+            log.debug("Saving changes!");
+            resolver.commit();
+
+            return FormActionResult.success("Profile Updated");
+
+        } 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());
+        return RESOURCE_TYPE.equals(actionResource.getResourceType());
     }
 
 }
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/UpdateUserGeneratedContentAction.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/UpdateUserGeneratedContentAction.java
new file mode 100644
index 0000000..2a94383
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/UpdateUserGeneratedContentAction.java
@@ -0,0 +1,104 @@
+/*
+ * 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.Collections;
+import java.util.Map;
+
+import org.apache.commons.text.StringSubstitutor;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ModifiableValueMap;
+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.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.cms.CMSConstants;
+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.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(service = FormAction.class, immediate = true)
+public class UpdateUserGeneratedContentAction implements FormAction {
+
+    private static final Logger log = LoggerFactory.getLogger(UpdateUserGeneratedContentAction.class);
+
+    private ResourceResolverFactory factory;
+
+    @Activate
+    public UpdateUserGeneratedContentAction(@Reference ResourceResolverFactory factory) {
+        this.factory = factory;
+    }
+
+    @Override
+    public FormActionResult handleForm(Resource actionResource, FormRequest request) throws FormException {
+        log.trace("handleForm");
+
+        ValueMap properties = actionResource.getValueMap();
+
+        try (ResourceResolver resolver = getResourceResolver()) {
+            StringSubstitutor sub = new StringSubstitutor(request.getFormData());
+
+            String path = sub.replace(properties.get("path", String.class));
+            log.debug("Updating UGC resource at path: {}", path);
+
+            Resource resource = resolver.getResource(path);
+            Resource ugcParent = findUgcParent(resource);
+
+            if (!request.getOriginalRequest().getResourceResolver().getUserID()
+                    .equals(ugcParent.getValueMap().get("user", String.class))) {
+                throw new FormException("Cannot modify content not created by the current user");
+            }
+
+            ModifiableValueMap mvm = resource.adaptTo(ModifiableValueMap.class);
+            Map<String, Object> formData = request.getFormData();
+            mvm.putAll(formData);
+            resolver.commit();
+            log.debug("Successfully persisted resource");
+            return FormActionResult.success("Updated resource");
+        } catch (LoginException | PersistenceException e) {
+            throw new FormException("Failed to update resource", e);
+        }
+    }
+
+    private Resource findUgcParent(Resource resource) throws FormException {
+        if (CMSConstants.NT_UGC.equals(resource.getResourceType())) {
+            return resource;
+        } else if (resource.getParent() != null) {
+            return findUgcParent(resource.getParent());
+        } else {
+            throw new FormException("Failed to find UGC Parent");
+        }
+    }
+
+    private ResourceResolver getResourceResolver() throws LoginException {
+        return factory.getServiceResourceResolver(
+                Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, "sling-cms-ugc"));
+    }
+
+    @Override
+    public boolean handles(Resource actionResource) {
+        return "reference/components/forms/actions/updateugc".equals(actionResource.getResourceType());
+    }
+
+}
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/UserGeneratedContentAction.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/UserGeneratedContentAction.java
index 084d17f..fb5fa17 100644
--- a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/UserGeneratedContentAction.java
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/actions/UserGeneratedContentAction.java
@@ -20,9 +20,10 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
 
-import org.apache.commons.text.StringSubstitutor;
 import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.text.StringSubstitutor;
 import org.apache.jackrabbit.JcrConstants;
 import org.apache.sling.api.resource.PersistenceException;
 import org.apache.sling.api.resource.Resource;
@@ -38,6 +39,7 @@ import org.apache.sling.cms.usergenerated.UGCBucketConfig;
 import org.apache.sling.cms.usergenerated.UserGeneratedContentService;
 import org.apache.sling.cms.usergenerated.UserGeneratedContentService.APPROVE_ACTION;
 import org.apache.sling.cms.usergenerated.UserGeneratedContentService.CONTENT_TYPE;
+import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
 import org.slf4j.Logger;
@@ -48,11 +50,15 @@ public class UserGeneratedContentAction implements FormAction {
 
     private static final Logger log = LoggerFactory.getLogger(UserGeneratedContentAction.class);
 
-    @Reference
-    private NameFilter filter;
+    private final NameFilter filter;
 
-    @Reference
-    private UserGeneratedContentService ugcService;
+    private final UserGeneratedContentService ugcService;
+
+    @Activate
+    public UserGeneratedContentAction(@Reference NameFilter filter, @Reference UserGeneratedContentService ugcService) {
+        this.filter = filter;
+        this.ugcService = ugcService;
+    }
 
     @Override
     public FormActionResult handleForm(Resource actionResource, FormRequest request) throws FormException {
@@ -82,7 +88,7 @@ public class UserGeneratedContentAction implements FormAction {
                     log.warn("Invalid value: {}", v);
                     return null;
                 }
-            }).forEach(v -> {
+            }).filter(Objects::nonNull).forEach(v -> {
                 log.debug("Adding additional property: {}", v);
                 contentProperties.put(v.getLeft(), v.getRight());
             });
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
index 955197b..8fd6e8f 100644
--- 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
@@ -27,18 +27,42 @@ 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.apache.sling.cms.reference.forms.FormUtils;
+import org.apache.sling.cms.reference.impl.SearchServiceImpl.Config;
+import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 @Component(service = FieldHandler.class)
+@Designate(ocd = Config.class)
 public class SelectionHandler implements FieldHandler {
 
+    public static final String DEFAULT_RESOURCE_TYPE = "reference/components/forms/fields/selection";
+
     private static final Logger log = LoggerFactory.getLogger(SelectionHandler.class);
 
+    private final Config config;
+
+    @ObjectClassDefinition(name = "%cms.reference.selection.name", description = "%cms.reference.selection.description", localization = "OSGI-INF/l10n/bundle")
+    public @interface Config {
+
+        @AttributeDefinition(name = "%cms.reference.supportedTypes.name", description = "%cms.reference.supportedTypes.description", defaultValue = {
+                DEFAULT_RESOURCE_TYPE })
+        String[] supportedTypes() default { DEFAULT_RESOURCE_TYPE };
+    }
+
+    @Activate
+    public SelectionHandler(Config config) {
+        this.config = config;
+    }
+
     @Override
     public boolean handles(Resource fieldResource) {
-        return "reference/components/forms/fields/selection".equals(fieldResource.getResourceType());
+        return FormUtils.handles(config.supportedTypes(), fieldResource);
     }
 
     @Override
@@ -72,6 +96,7 @@ public class SelectionHandler implements FieldHandler {
                 formData.put(name, value);
             }
         }
+
     }
 
     private String[] stripBlank(String[] parameterValues) {
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
index 0b27346..13839ff 100644
--- 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
@@ -23,19 +23,42 @@ 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.apache.sling.cms.reference.forms.FormUtils;
+import org.apache.sling.cms.reference.forms.impl.fields.TextareaHandler.Config;
+import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 @Component(service = FieldHandler.class)
+@Designate(ocd = Config.class)
 public class TextareaHandler implements FieldHandler {
 
+    public static final String DEFAULT_RESOURCE_TYPE = "reference/components/forms/fields/textarea";
+
     private static final Logger log = LoggerFactory.getLogger(TextareaHandler.class);
 
+    private Config config;
+
+    @ObjectClassDefinition(name = "%cms.reference.textarea.name", description = "%cms.reference.textarea.description", localization = "OSGI-INF/l10n/bundle")
+    public @interface Config {
+
+        @AttributeDefinition(name = "%cms.reference.supportedTypes.name", description = "%cms.reference.supportedTypes.description", defaultValue = {
+                DEFAULT_RESOURCE_TYPE })
+        String[] supportedTypes() default { DEFAULT_RESOURCE_TYPE };
+    }
+
+    @Activate
+    public TextareaHandler(Config config) {
+        this.config = config;
+    }
+
     @Override
     public boolean handles(Resource fieldResource) {
-        String resourceType = fieldResource.getResourceType();
-        return "reference/components/forms/fields/textarea".equals(resourceType);
+        return FormUtils.handles(config.supportedTypes(), fieldResource);
     }
 
     @Override
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
index 7622a56..f498586 100644
--- 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
@@ -30,15 +30,22 @@ import org.apache.sling.api.request.RequestParameter;
 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.apache.sling.cms.reference.forms.FormUtils;
+import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 @Component(service = FieldHandler.class)
+@Designate(ocd = TextfieldHandler.Config.class)
 public class TextfieldHandler implements FieldHandler {
 
     private static final Logger log = LoggerFactory.getLogger(TextfieldHandler.class);
     private static final Map<String, String> typePatterns = new HashMap<>();
+    public static final String DEFAULT_RESOURCE_TYPE = "reference/components/forms/fields/textfield";
     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}");
@@ -52,10 +59,24 @@ public class TextfieldHandler implements FieldHandler {
         dateFormats.put("datetime-local", "yyyy-MM-ddThh:mm");
     }
 
+    private Config config;
+
+    @ObjectClassDefinition(name = "%cms.reference.textfield.name", description = "%cms.reference.textfield.description", localization = "OSGI-INF/l10n/bundle")
+    public @interface Config {
+
+        @AttributeDefinition(name = "%cms.reference.supportedTypes.name", description = "%cms.reference.supportedTypes.description", defaultValue = {
+                DEFAULT_RESOURCE_TYPE })
+        String[] supportedTypes() default { DEFAULT_RESOURCE_TYPE };
+    }
+
+    @Activate
+    public TextfieldHandler(Config config) {
+        this.config = config;
+    }
+
     @Override
     public boolean handles(Resource fieldResource) {
-        String resourceType = fieldResource.getResourceType();
-        return "reference/components/forms/fields/textfield".equals(resourceType);
+        return FormUtils.handles(config.supportedTypes(), fieldResource);
     }
 
     @Override
@@ -89,7 +110,6 @@ public class TextfieldHandler implements FieldHandler {
                     formData.put(name + ".contentType", param.getContentType());
                 }
             } else if ("date".equals(saveAs)) {
-
                 if (!dateFormats.containsKey(type)) {
                     throw new FormException("Field " + name + " is not a date type");
                 }
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/providers/RequestParametersValueProvider.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/providers/RequestParametersValueProvider.java
new file mode 100644
index 0000000..ca08dee
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/providers/RequestParametersValueProvider.java
@@ -0,0 +1,56 @@
+/*
+ * 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.Map;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+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 RequestParametersValueProvider implements FormValueProvider {
+
+    private static final Logger log = LoggerFactory.getLogger(RequestParametersValueProvider.class);
+
+    @Override
+    public void loadValues(SlingHttpServletRequest request, Resource providerResource, Map<String, Object> formData) {
+        log.trace("loadFormData");
+        String[] parameters = providerResource.getValueMap().get("allowedParameters", String[].class);
+        if (parameters != null) {
+            Arrays.stream(parameters).forEach(p -> {
+                if (request.getParameter(p) != null) {
+                    if (request.getParameterValues(p).length > 1) {
+                        formData.put(p, request.getParameterValues(p));
+                    } else {
+                        formData.put(p, request.getParameter(p));
+                    }
+                }
+            });
+        }
+
+    }
+
+    @Override
+    public boolean handles(Resource valueProviderResource) {
+        return "reference/components/forms/providers/requestparameters".equals(valueProviderResource.getResourceType());
+    }
+}
diff --git a/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/providers/SuffixResourceFormValueProvider.java b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/providers/SuffixResourceFormValueProvider.java
new file mode 100644
index 0000000..15e57c6
--- /dev/null
+++ b/reference/src/main/java/org/apache/sling/cms/reference/forms/impl/providers/SuffixResourceFormValueProvider.java
@@ -0,0 +1,63 @@
+/*
+ * 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.Map;
+
+import org.apache.commons.text.StringSubstitutor;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.cms.reference.forms.FormConstants;
+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 SuffixResourceFormValueProvider implements FormValueProvider {
+
+    private static final Logger log = LoggerFactory.getLogger(SuffixResourceFormValueProvider.class);
+
+    @Override
+    public void loadValues(SlingHttpServletRequest request, Resource providerResource, Map<String, Object> formData) {
+        log.trace("loadValues");
+        Resource suffixResource = request.getRequestPathInfo().getSuffixResource();
+
+        ValueMap providerProperties = providerResource.getValueMap();
+        StringSubstitutor sub = new StringSubstitutor(formData);
+        String basePath = sub.replace(providerProperties.get("basePath", String.class));
+        String[] allowedProperties = providerProperties.get(FormConstants.PN_ALLOWED_PROPERTIES, String[].class);
+        if (suffixResource != null && suffixResource.getPath().startsWith(basePath)) {
+            ValueMap suffixProperties = suffixResource.getValueMap();
+            if (allowedProperties != null && allowedProperties.length > 0) {
+                Arrays.stream(allowedProperties).filter(suffixProperties::containsKey)
+                        .forEach(p -> formData.put(p, suffixProperties.get(p)));
+            } else {
+                formData.putAll(suffixProperties);
+            }
+            formData.put("suffixResource", suffixResource.getPath());
+        }
+
+    }
+
+    @Override
+    public boolean handles(Resource valueProviderResource) {
+        return "reference/components/forms/providers/suffixresource".equals(valueProviderResource.getResourceType());
+    }
+}
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
index 4219be9..323b51a 100644
--- 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
@@ -32,6 +32,7 @@ import org.apache.jackrabbit.api.security.user.UserManager;
 import org.apache.sling.api.SlingHttpServletRequest;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.cms.reference.forms.FormConstants;
 import org.apache.sling.cms.reference.forms.FormValueProvider;
 import org.osgi.service.component.annotations.Component;
 import org.slf4j.Logger;
@@ -53,7 +54,7 @@ public class UserProfileFormValueProvider implements FormValueProvider {
                 UserManager userManager = session.getUserManager();
                 User user = (User) userManager.getAuthorizable(userId);
 
-                String subpath = providerResource.getValueMap().get("subpath", "profile");
+                String subpath = providerResource.getValueMap().get(FormConstants.PN_SUBPATH, FormConstants.PATH_PROFILE);
                 log.debug("Loading profile data from: {}/{}", user.getPath(), subpath);
 
                 Iterator<String> keys = user.getPropertyNames(subpath);
diff --git a/reference/src/main/resources/OSGI-INF/l10n/bundle.properties b/reference/src/main/resources/OSGI-INF/l10n/bundle.properties
index 0911dc9..a4d182f 100644
--- a/reference/src/main/resources/OSGI-INF/l10n/bundle.properties
+++ b/reference/src/main/resources/OSGI-INF/l10n/bundle.properties
@@ -23,7 +23,7 @@
 # the Sling SCR plugin
 
 ## Search Service Entries
-cms.reference.search.name=Apache Sling Reference Search Configuration
+cms.reference.search.name=Apache Sling CMS - Reference Search Configuration
 cms.reference.search.description=Configuration for the Reference \
 Search component
 
@@ -32,7 +32,7 @@ searchServiceUsername.description=The name of a service user to use \
 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.name=Apache Sling CMS - Reference Send Email Form Action
 cms.reference.sendemail.description=A reference form action for sending a \
 simple text email
 
@@ -44,7 +44,6 @@ 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
@@ -55,4 +54,17 @@ 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
+to the SMTP server
+
+# Form configuration titles
+cms.reference.supportedTypes.name=Supported Types
+cms.reference.supportedTypes.description=The resource types supported by this action
+
+cms.reference.selection.name=Apache Sling CMS - Reference Selection Field
+cms.reference.selection.description=Configuration for the select form field
+
+cms.reference.textarea.name=Apache Sling CMS - Reference Textarea Field
+cms.reference.selection.description=Configuration for the textarea form field
+
+cms.reference.textfield.name=pache Sling CMS - Reference Text field
+cms.reference.textfield.description=Configuration for the text field
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/createuser.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/createuser.json
new file mode 100644
index 0000000..7e59253
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/createuser.json
@@ -0,0 +1,5 @@
+{
+    "jcr:primaryType" : "sling:Component",
+    "jcr:title": "Create User",
+    "componentType": "Form Action"
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/createuser/createuser.jsp b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/createuser/createuser.jsp
new file mode 100644
index 0000000..b6c450f
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/createuser/createuser.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>Create User</h3>
+    <dl>
+        <dt>Group Membership</dt>
+        <dd>${sling:encode(fn:join(properties.groups,','),'HTML')}</dd>
+        <dt>Intermediate Path</dt>
+        <dd>${sling:encode(properties.intermediatePath,'HTML')}</dd>
+        <dt>Service User</dt>
+        <dd>${sling:encode(properties.serviceUser,'HTML')}</dd>
+        <dt>Set Profile Properties</dt>
+        <dd>${sling:encode(fn:join(properties.profileProperties,','),'HTML')}</dd>
+    </dl>
+</c:if>
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/createuser/edit.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/createuser/edit.json
new file mode 100644
index 0000000..3af280d
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/createuser/edit.json
@@ -0,0 +1,47 @@
+{
+    "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",
+        "groups": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/repeating",
+            "label": "Group Membership",
+            "name": "groups"
+        },
+        "groupsTypeHint": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+            "name": "groups@TypeHint",
+            "value": "String[]"
+        },
+        "profileProperties": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/repeating",
+            "label": "Profile Properties",
+            "name": "profileProperties"
+        },
+        "profilePropertiesTypeHint": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+            "name": "profileProperties@TypeHint",
+            "value": "String[]"
+        },
+        "intermediatePath": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Intermediate Path",
+            "name": "intermediatePath",
+            "required": true
+        },
+        "serviceUser": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Service User",
+            "name": "serviceUser",
+            "required": true
+        }
+    }
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/deleteugc.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/deleteugc.json
new file mode 100644
index 0000000..5ce7fe5
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/deleteugc.json
@@ -0,0 +1,5 @@
+{
+    "jcr:primaryType" : "sling:Component",
+    "jcr:title": "Delete User Generated Content",
+    "componentType": "Form Action"
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/deleteugc/deleteugc.jsp b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/deleteugc/deleteugc.jsp
new file mode 100644
index 0000000..a6a6fea
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/deleteugc/deleteugc.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>Delete User Generated Content</h3>
+    <dl>
+        <dt>Path</dt>
+        <dd>${sling:encode(properties.path,'HTML')}</dd>
+    </dl>
+</c:if>
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/deleteugc/edit.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/deleteugc/edit.json
new file mode 100644
index 0000000..759c096
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/deleteugc/edit.json
@@ -0,0 +1,14 @@
+{
+    "jcr:primaryType": "nt:unstructured",
+    "sling:resourceType": "sling-cms/components/editor/slingform",
+    "button": "Save",
+    "fields": {
+        "path": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/path",
+            "label": "Path",
+            "name": "path",
+            "required": true
+        }
+    }
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/requestpasswordreset/edit.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/requestpasswordreset/edit.json
new file mode 100644
index 0000000..760ab09
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/requestpasswordreset/edit.json
@@ -0,0 +1,16 @@
+{
+    "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",
+        "resettimeout": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/text",
+            "label": "Reset Timeout",
+            "name": "resettimeout",
+            "type": "number"
+        }
+    }
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/requestpasswordreset/requestpasswordreset.jsp b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/requestpasswordreset/requestpasswordreset.jsp
new file mode 100644
index 0000000..becd97e
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/requestpasswordreset/requestpasswordreset.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>Request Password Reset</h3>
+    <dl>
+        <dt>Reset Timeout</dt>
+        <dd>${sling:encode(properties.resettimeout,'HTML')}</dd>
+    </dl>
+</c:if>
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/resetpassword.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/resetpassword.json
new file mode 100644
index 0000000..0635a1b
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/resetpassword.json
@@ -0,0 +1,5 @@
+{
+    "jcr:primaryType" : "sling:Component",
+    "jcr:title": "Reset Password",
+    "componentType": "Form Action"
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/resetpassword/edit.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/resetpassword/edit.json
new file mode 100644
index 0000000..4f8a34b
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/resetpassword/edit.json
@@ -0,0 +1,5 @@
+{
+    "jcr:primaryType": "nt:unstructured",
+    "sling:resourceType": "sling-cms/components/editor/slingform",
+    "button": "No need to edit"
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/resetpassword/resetpassword.jsp b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/resetpassword/resetpassword.jsp
new file mode 100644
index 0000000..7c9b003
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/resetpassword/resetpassword.jsp
@@ -0,0 +1,22 @@
+<%-- /*
+ * 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>Reset Password</h3>
+</c:if>
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/updateugc.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/updateugc.json
new file mode 100644
index 0000000..e1f4e64
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/updateugc.json
@@ -0,0 +1,5 @@
+{
+    "jcr:primaryType" : "sling:Component",
+    "jcr:title": "Update User Generated Content",
+    "componentType": "Form Action"
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/updateugc/edit.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/updateugc/edit.json
new file mode 100644
index 0000000..759c096
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/updateugc/edit.json
@@ -0,0 +1,14 @@
+{
+    "jcr:primaryType": "nt:unstructured",
+    "sling:resourceType": "sling-cms/components/editor/slingform",
+    "button": "Save",
+    "fields": {
+        "path": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/path",
+            "label": "Path",
+            "name": "path",
+            "required": true
+        }
+    }
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/updateugc/updateugc.jsp b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/updateugc/updateugc.jsp
new file mode 100644
index 0000000..cdbf3fe
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/actions/updateugc/updateugc.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 User Generated Content</h3>
+    <dl>
+        <dt>Path</dt>
+        <dd>${sling:encode(properties.path,'HTML')}</dd>
+    </dl>
+</c:if>
\ 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
index e046a87..e2ca9cf 100644
--- 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
@@ -39,9 +39,9 @@
             <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"' : ''}
+    <textarea class="${sling:encode(formConfig.fieldClass,'HTML')}" id="${sling:encode(properties.name,'HTML')}" name="${sling:encode(properties.name,'HTML')}" ${properties.required ? 'required="required"' : ''}
         <c:forEach var="attr" items="${properties.additionalAttributes}">
             ${fn:split(attr,'\\=')[0]}="${fn:split(attr,'\\=')[1]}"
         </c:forEach>
-        >${fieldValue}</textarea>
+        >${sling:encode(fieldValue,'HTML')}</textarea>
 </div>
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
index 09d5cfa..4adc29c 100644
--- 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
@@ -41,7 +41,7 @@
             <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"' : ''}
+    <input type="${sling:encode(properties.type,'HTML_ATTR')}" class="${sling:encode(formConfig.fieldClass,'HTML_ATTR')}" id="${sling:encode(properties.name,'HTML_ATTR')}" name="${sling:encode(properties.name,'HTML_ATTR')}" value="${sling:encode(fieldValue,'HTML_ATTR')}" ${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> 
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
index aa98ccd..422d9d3 100644
--- 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
@@ -19,6 +19,12 @@
             "name": "submitText",
             "required": true
         },
+        "errorPage": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/path",
+            "label": "Error Page",
+            "name": "errorPage"
+        },
         "successPage": {
             "jcr:primaryType": "nt:unstructured",
             "sling:resourceType": "sling-cms/components/editor/fields/path",
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/login.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/login.json
new file mode 100644
index 0000000..39fbbdc
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/login.json
@@ -0,0 +1,5 @@
+{
+    "jcr:primaryType" : "sling:Component",
+    "jcr:title": "Login",
+    "componentType": "General"
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/login/edit.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/login/edit.json
new file mode 100644
index 0000000..1f19887
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/login/edit.json
@@ -0,0 +1,46 @@
+{
+	"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",
+		"errorMessage": {
+			"jcr:primaryType": "nt:unstructured",
+			"sling:resourceType": "sling-cms/components/editor/fields/richtext",
+			"label": "Error Message",
+			"name": "errorMessage",
+			"required": true
+		},
+		"usernameLabel": {
+			"jcr:primaryType": "nt:unstructured",
+			"sling:resourceType": "sling-cms/components/editor/fields/text",
+			"defaultValue": "Submit",
+			"label": "Username Label",
+			"name": "usernameLabel",
+			"required": true
+		},
+		"passwordLabel": {
+			"jcr:primaryType": "nt:unstructured",
+			"sling:resourceType": "sling-cms/components/editor/fields/text",
+			"defaultValue": "Submit",
+			"label": "Password Label",
+			"name": "passwordLabel",
+			"required": true
+		},
+		"submitLabel": {
+			"jcr:primaryType": "nt:unstructured",
+			"sling:resourceType": "sling-cms/components/editor/fields/text",
+			"defaultValue": "Submit",
+			"label": "Submit Label",
+			"name": "submitLabel",
+			"required": true
+		},
+		"successPage": {
+			"jcr:primaryType": "nt:unstructured",
+			"sling:resourceType": "sling-cms/components/editor/fields/path",
+			"label": "Success Page",
+			"name": "successPage"
+		}
+	}
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/login/login.jsp b/reference/src/main/resources/jcr_root/apps/reference/components/forms/login/login.jsp
new file mode 100644
index 0000000..15d0957
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/login/login.jsp
@@ -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.
+ */ --%>
+<%@include file="/libs/sling-cms/global.jsp"%>
+<sling:adaptTo adaptable="${resource}" adaptTo="org.apache.sling.cms.PageManager" var="pageManager" />
+<sling:adaptTo adaptable="${resource}" adaptTo="org.apache.sling.cms.ComponentPolicyManager" var="componentPolicyMgr" />
+<c:set var="formConfig" value="${componentPolicyMgr.componentPolicy.componentConfigs['reference/components/forms/form'].valueMap}" />
+<form class="${formConfig.formClass}" action="${pageManager.page.path}.allowpost.html/j_security_check" method="post" data-analytics-id="Login Form">  
+    <c:if test="${not empty param.j_reason}">
+        <div class="${formConfig.alertClass}">
+            ${sling:encode(properties.errorMessage,'HTML')}
+        </div>
+    </c:if>
+    <div class="${formConfig.fieldGroupClass}">
+        <label for="j_username" class="label">${sling:encode(properties.usernameLabel,'HTML')} <span class="${formConfig.fieldRequiredClass}">*</span></label>
+        <input type="text" class="${formConfig.fieldClass}" required="required" name="j_username" />
+    </div>
+    <div class="${formConfig.fieldGroupClass}">
+        <label for="j_password" class="label">${sling:encode(properties.passwordLabel,'HTML')} <span class="${formConfig.fieldRequiredClass}">*</span></label>
+        <input type="password" class="${formConfig.fieldClass}" required="required" name="j_password" />
+    </div>
+    <input type="hidden" name="resource" value="${sling:encode(properties.successPage,'HTML_ATTR')}.html" />
+    <input type="hidden" name="j_validate" value="true" />
+    <div class="${formConfig.fieldGroupClass}">
+        <button class="${formConfig.submitClass}">${sling:encode(properties.submitLabel,'HTML')}</button>
+    </div>
+</form>
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/requestparameters.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/requestparameters.json
new file mode 100644
index 0000000..802f8db
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/requestparameters.json
@@ -0,0 +1,6 @@
+{
+    "jcr:primaryType" : "sling:Component",
+    "jcr:title": "Request Parameters",
+    "componentType": "Form Value Provider",
+    "reloadPage": true
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/requestparameters/edit.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/requestparameters/edit.json
new file mode 100644
index 0000000..1fccbaf
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/requestparameters/edit.json
@@ -0,0 +1,21 @@
+{
+    "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/repeating",
+            "label": "Allowed Parameters",
+            "name": "allowedParameters"
+        },
+        "subpathTypeHint": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+            "name": "allowedParameters@TypeHint",
+            "value": "String[]"
+        }
+    }
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/requestparameters/requestparameters.jsp b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/requestparameters/requestparameters.jsp
new file mode 100644
index 0000000..aad2f64
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/requestparameters/requestparameters.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>Request Parameters</h3>
+    <dl>
+        <dt>Allowed Parameters</dt>
+        <dd>${sling:encode(fn:join(properties.allowedParameters,','),'HTML')}</dd>
+    </dl>
+</c:if>
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/suffixresource.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/suffixresource.json
new file mode 100644
index 0000000..e497a9c
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/suffixresource.json
@@ -0,0 +1,6 @@
+{
+    "jcr:primaryType" : "sling:Component",
+    "jcr:title": "Suffix Resource",
+    "componentType": "Form Value Provider",
+    "reloadPage": true
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/suffixresource/edit.json b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/suffixresource/edit.json
new file mode 100644
index 0000000..1e67bbb
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/suffixresource/edit.json
@@ -0,0 +1,28 @@
+{
+    "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",
+        "allowedProperties": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/repeating",
+            "label": "Allowed Properties",
+            "name": "allowedProperties"
+        },
+        "allowedPropertiesTypeHint": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/hidden",
+            "name": "allowedProperties@TypeHint",
+            "value": "String[]"
+        },
+        "basePath": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "sling-cms/components/editor/fields/path",
+            "label": "Base Path",
+            "name": "basePath",
+            "required": true
+        }
+    }
+}
\ No newline at end of file
diff --git a/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/suffixresource/suffixresource.jsp b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/suffixresource/suffixresource.jsp
new file mode 100644
index 0000000..c702d5f
--- /dev/null
+++ b/reference/src/main/resources/jcr_root/apps/reference/components/forms/providers/suffixresource/suffixresource.jsp
@@ -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.
+ */ --%>
+ <%@include file="/libs/sling-cms/global.jsp"%>
+<c:if test="${cmsEditEnabled == 'true'}">
+    <h3>Suffix Resource</h3>
+    <dl>
+        <dt>Allowed Properties</dt>
+        <dd>${sling:encode(fn:join(properties.allowedProperties,','),'HTML')}</dd>
+        <dt>Base Path</dt>
+        <dd>${sling:encode(properties.basePath,'HTML')}</dd>
+    </dl>
+</c:if>
\ No newline at end of file
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/FormActionResultTest.java b/reference/src/test/java/org/apache/sling/cms/reference/forms/FormActionResultTest.java
index 9ed86d7..9f7b5d0 100644
--- a/reference/src/test/java/org/apache/sling/cms/reference/forms/FormActionResultTest.java
+++ b/reference/src/test/java/org/apache/sling/cms/reference/forms/FormActionResultTest.java
@@ -20,8 +20,6 @@ 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 {
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/FormHandlerTest.java b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/FormHandlerTest.java
index 048d08d..ab0e2ab 100644
--- a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/FormHandlerTest.java
+++ b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/FormHandlerTest.java
@@ -17,10 +17,10 @@
 package org.apache.sling.cms.reference.forms.impl;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.never;
 
 import java.io.IOException;
+import java.lang.annotation.Annotation;
 import java.util.Arrays;
 
 import javax.servlet.ServletException;
@@ -62,20 +62,49 @@ public class FormHandlerTest {
         context.currentResource(Mockito.mock(Resource.class));
 
         formRequest = new FormRequestImpl(context.request(), null,
-                Arrays.asList(new SelectionHandler(), new TextareaHandler(), new TextfieldHandler()));
+                Arrays.asList(new SelectionHandler(new SelectionHandler.Config() {
 
-        final SendEmailAction sendEmailAction = new SendEmailAction();
-        mailService = Mockito.mock(MailService.class);
-        Mockito.when(mailService.getMessageBuilder()).thenReturn(new MockMessageBuilder());
-        sendEmailAction.setMailService(mailService);
+                    @Override
+                    public Class<? extends Annotation> annotationType() {
+                        return null;
+                    }
+
+                    @Override
+                    public String[] supportedTypes() {
+                        return new String[] { SelectionHandler.DEFAULT_RESOURCE_TYPE };
+                    }
+
+                }), new TextareaHandler(new TextareaHandler.Config() {
+
+                    @Override
+                    public Class<? extends Annotation> annotationType() {
+                        return null;
+                    }
+
+                    @Override
+                    public String[] supportedTypes() {
+                        return new String[] { TextareaHandler.DEFAULT_RESOURCE_TYPE };
+                    }
 
-        formHandler = new FormHandler(Arrays.asList(sendEmailAction)) {
-            private static final long serialVersionUID = 1L;
+                }), new TextfieldHandler(new TextfieldHandler.Config() {
 
-            protected FormRequest getFormRequest(final SlingHttpServletRequest request) {
-                return formRequest;
-            }
-        };
+                    @Override
+                    public Class<? extends Annotation> annotationType() {
+                        return null;
+                    }
+
+                    @Override
+                    public String[] supportedTypes() {
+                        return new String[] { TextfieldHandler.DEFAULT_RESOURCE_TYPE };
+                    }
+
+                })));
+        context.registerAdapter(SlingHttpServletRequest.class, FormRequest.class, formRequest);
+
+        mailService = Mockito.mock(MailService.class);
+        Mockito.when(mailService.getMessageBuilder()).thenReturn(new MockMessageBuilder());
+        final SendEmailAction sendEmailAction = new SendEmailAction(mailService);
+        formHandler = new FormHandler(Arrays.asList(sendEmailAction));
     }
 
     @Test
@@ -95,7 +124,7 @@ public class FormHandlerTest {
 
         formHandler.service(context.request(), context.response());
 
-        assertTrue(HttpServletResponse.SC_MOVED_TEMPORARILY == context.response().getStatus());
+        assertEquals(HttpServletResponse.SC_MOVED_TEMPORARILY, context.response().getStatus());
         assertEquals("/form-no-actions.html?error=actions", context.response().getHeader("Location"));
         Mockito.verify(mailService, never()).sendMessage(Mockito.any());
     }
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/FormRequestImplTest.java b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/FormRequestImplTest.java
index 824056d..1c3285c 100644
--- a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/FormRequestImplTest.java
+++ b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/FormRequestImplTest.java
@@ -21,6 +21,8 @@ import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import java.io.ByteArrayInputStream;
+import java.lang.annotation.Annotation;
 import java.util.Arrays;
 
 import com.google.common.collect.ImmutableMap;
@@ -49,10 +51,46 @@ public class FormRequestImplTest {
                 .setParameterMap(ImmutableMap.<String, Object>builder().put("requiredtextarea", "Hello World!")
                         .put("singleselect", "Hello World!").put("anotherkey", "Hello World!").put("money", "123")
                         .put("patternfield", "123").put("double", "2.7").put("integer", "2")
-                        .put("datefield", "2019-02-02").build());
+                        .put("file", new ByteArrayInputStream(new byte[0])).put("datefield", "2019-02-02").build());
 
         formRequest = new FormRequestImpl(context.request(), null,
-                Arrays.asList(new SelectionHandler(), new TextareaHandler(), new TextfieldHandler()));
+                Arrays.asList(new SelectionHandler(new SelectionHandler.Config() {
+
+                    @Override
+                    public Class<? extends Annotation> annotationType() {
+                        return null;
+                    }
+
+                    @Override
+                    public String[] supportedTypes() {
+                        return new String[] { SelectionHandler.DEFAULT_RESOURCE_TYPE };
+                    }
+
+                }), new TextareaHandler(new TextareaHandler.Config() {
+
+                    @Override
+                    public Class<? extends Annotation> annotationType() {
+                        return null;
+                    }
+
+                    @Override
+                    public String[] supportedTypes() {
+                        return new String[] { TextareaHandler.DEFAULT_RESOURCE_TYPE };
+                    }
+
+                }), new TextfieldHandler(new TextfieldHandler.Config() {
+
+                    @Override
+                    public Class<? extends Annotation> annotationType() {
+                        return null;
+                    }
+
+                    @Override
+                    public String[] supportedTypes() {
+                        return new String[] { TextfieldHandler.DEFAULT_RESOURCE_TYPE };
+                    }
+
+                })));
         formRequest.initFields();
     }
 
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/CreateUserActionTest.java b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/CreateUserActionTest.java
new file mode 100644
index 0000000..bb767c5
--- /dev/null
+++ b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/CreateUserActionTest.java
@@ -0,0 +1,153 @@
+/*
+ * 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 static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.ValueFactory;
+import javax.jcr.ValueFormatException;
+
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.value.AbstractValueFactory;
+import org.apache.sling.api.resource.LoginException;
+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.cms.reference.forms.FormActionResult;
+import org.apache.sling.cms.reference.forms.FormConstants;
+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.servlethelpers.MockSlingHttpServletRequest;
+import org.apache.sling.testing.resourceresolver.MockResource;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class CreateUserActionTest {
+
+    private ResourceResolverFactory factory;
+    private ResourceResolver resolver;
+    private MockResource actionResource;
+
+    @Before
+    public void init() throws FormException, LoginException, AccessDeniedException,
+            UnsupportedRepositoryOperationException, RepositoryException {
+
+        factory = Mockito.mock(ResourceResolverFactory.class);
+
+        resolver = Mockito.mock(ResourceResolver.class);
+        Mockito.when(resolver.getResource(Mockito.any())).thenReturn(Mockito.mock(Resource.class));
+
+        Mockito.when(factory.getServiceResourceResolver(Mockito.anyMap())).thenReturn(resolver);
+
+        JackrabbitSession session = Mockito.mock(JackrabbitSession.class);
+        Mockito.when(resolver.adaptTo(Mockito.any())).thenReturn(session);
+
+        ValueFactory vf = new AbstractValueFactory() {
+
+            @Override
+            protected void checkPathFormat(String pathValue) throws ValueFormatException {
+                // TODO Auto-generated method stub
+
+            }
+
+            @Override
+            protected void checkNameFormat(String nameValue) throws ValueFormatException {
+                // TODO Auto-generated method stub
+
+            }
+
+        };
+        Mockito.when(session.getValueFactory()).thenReturn(vf);
+
+        UserManager userManager = Mockito.mock(UserManager.class);
+        Mockito.when(session.getUserManager()).thenReturn(userManager);
+
+        User existingUser = Mockito.mock(User.class);
+        Mockito.when(userManager.getAuthorizable("existing@email.com")).thenReturn(existingUser);
+        Mockito.when(existingUser.getPath()).thenReturn("/home");
+
+        Group agroup = Mockito.mock(Group.class);
+        Mockito.when(agroup.isGroup()).thenReturn(true);
+        Mockito.when(userManager.getAuthorizable("group1")).thenReturn(agroup);
+
+        Map<String, Object> properties = new HashMap<>();
+        properties.put(CreateUserAction.PN_INTERMEDIATE_PATH, "app");
+        properties.put(CreateUserAction.GROUPS, new String[] { "group1" });
+        properties.put(CreateUserAction.PROFILE_PROPERTIES, new String[] { "profile", "anotherfield" });
+        actionResource = new MockResource("/content", properties, null);
+
+        Mockito.when(
+                userManager.createUser(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.anyString()))
+                .thenReturn(existingUser);
+
+    }
+
+    @Test
+    public void testHandleForm() throws FormException, RepositoryException {
+
+        CreateUserAction action = new CreateUserAction(factory);
+
+        Mockito.when(resolver.getUserID()).thenReturn("valid@email.com");
+
+        FormRequest request = new FormRequestImpl(new MockSlingHttpServletRequest(resolver), null, null);
+        request.getFormData().put(CreateUserAction.PN_USERNAME, "new@email.com");
+        request.getFormData().put(FormConstants.PN_PASSWORD, "password1");
+        request.getFormData().put("profile", "value");
+        request.getFormData().put(CreateUserAction.GROUPS, new String[] { "group1" });
+
+        FormActionResult result = action.handleForm(actionResource, request);
+        assertTrue(result.isSucceeded());
+    }
+
+    @Test
+    public void testExistingUser() throws FormException, RepositoryException {
+
+        CreateUserAction action = new CreateUserAction(factory);
+
+        FormRequest request = new FormRequestImpl(new MockSlingHttpServletRequest(resolver), null, null);
+        request.getFormData().put(CreateUserAction.PN_USERNAME, "existing@email.com");
+        request.getFormData().put(FormConstants.PN_PASSWORD, "password1");
+
+        FormActionResult result = action.handleForm(actionResource, request);
+        assertFalse(result.isSucceeded());
+    }
+
+    @Test
+    public void testHandles() throws FormException {
+        CreateUserAction action = new CreateUserAction(factory);
+        Resource validResource = Mockito.mock(Resource.class);
+        Mockito.when(validResource.getResourceType()).thenReturn(CreateUserAction.RESOURCE_TYPE);
+        assertTrue(action.handles(validResource));
+
+        Resource inValidResource = Mockito.mock(Resource.class);
+        Mockito.when(inValidResource.getResourceType()).thenReturn("something/else");
+        assertFalse(action.handles(inValidResource));
+    }
+
+}
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/DeleteUserGeneratedContentActionTest.java b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/DeleteUserGeneratedContentActionTest.java
new file mode 100644
index 0000000..b91c14a
--- /dev/null
+++ b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/DeleteUserGeneratedContentActionTest.java
@@ -0,0 +1,153 @@
+/*
+ * 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 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.Collections;
+import java.util.HashMap;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ModifiableValueMap;
+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.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.wrappers.ModifiableValueMapDecorator;
+import org.apache.sling.api.wrappers.ValueMapDecorator;
+import org.apache.sling.cms.CMSConstants;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.apache.sling.cms.reference.forms.FormRequest;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class DeleteUserGeneratedContentActionTest {
+
+    @Test
+    public void testHandles() {
+
+        DeleteUserGeneratedContentAction action = new DeleteUserGeneratedContentAction(null);
+
+        Resource validResource = Mockito.mock(Resource.class);
+        Mockito.when(validResource.getResourceType()).thenReturn("reference/components/forms/actions/deleteugc");
+        assertTrue(action.handles(validResource));
+
+        Resource invalidResource = Mockito.mock(Resource.class);
+        Mockito.when(invalidResource.getResourceType())
+                .thenReturn("reference/components/forms/actions/someotheraction");
+        assertFalse(action.handles(invalidResource));
+    }
+
+    @Test
+    public void testInvalidServiceUser() throws FormException, LoginException {
+
+        LoginException le = new LoginException();
+        ResourceResolverFactory factory = Mockito.mock(ResourceResolverFactory.class);
+        Mockito.when(factory.getServiceResourceResolver(Mockito.anyMap())).thenThrow(le);
+
+        DeleteUserGeneratedContentAction action = new DeleteUserGeneratedContentAction(factory);
+
+        Resource actionResource = Mockito.mock(Resource.class);
+
+        FormRequest formRequest = Mockito.mock(FormRequest.class);
+
+        try {
+            action.handleForm(actionResource, formRequest);
+            fail();
+        } catch (FormException fe) {
+            assertEquals(le, fe.getCause());
+        }
+    }
+
+    @Test
+    public void testHandleForm() throws FormException, LoginException, PersistenceException {
+
+        ModifiableValueMap contentData = new ModifiableValueMapDecorator(new HashMap<String, Object>());
+        contentData.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+        Resource contentResource = Mockito.mock(Resource.class);
+        Mockito.when(contentResource.getValueMap()).thenReturn(contentData);
+        Mockito.when(contentResource.adaptTo(Mockito.any())).thenReturn(contentData);
+
+        Resource ugcResource = Mockito.mock(Resource.class);
+        Mockito.when(ugcResource.getResourceType()).thenReturn(CMSConstants.NT_UGC);
+        Mockito.when(ugcResource.getValueMap())
+                .thenReturn(new ValueMapDecorator(Collections.singletonMap("user", "auser")));
+        Mockito.when(contentResource.getParent()).thenReturn(ugcResource);
+
+        ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+        Mockito.when(resolver.getUserID()).thenReturn("auser");
+        SlingHttpServletRequest request = Mockito.mock(SlingHttpServletRequest.class);
+        Mockito.when(request.getResourceResolver()).thenReturn(resolver);
+        Mockito.when(resolver.getResource("/etc/usergenerated/test")).thenReturn(contentResource);
+
+        ResourceResolverFactory factory = Mockito.mock(ResourceResolverFactory.class);
+        Mockito.when(factory.getServiceResourceResolver(Mockito.anyMap())).thenReturn(resolver);
+
+        DeleteUserGeneratedContentAction action = new DeleteUserGeneratedContentAction(factory);
+
+        ValueMap actionData = new ModifiableValueMapDecorator(new HashMap<String, Object>());
+        actionData.put("path", "/etc/usergenerated/test");
+
+        Resource actionResource = Mockito.mock(Resource.class);
+        Mockito.when(actionResource.getValueMap()).thenReturn(actionData);
+
+        FormRequest formRequest = Mockito.mock(FormRequest.class);
+        Mockito.when(formRequest.getFormData()).thenReturn(new ValueMapDecorator(Collections.emptyMap()));
+        Mockito.when(formRequest.getOriginalRequest()).thenReturn(request);
+
+        action.handleForm(actionResource, formRequest);
+
+        Mockito.verify(resolver).delete(ugcResource);
+
+    }
+
+    @Test
+    public void testNoParent() throws FormException, LoginException {
+
+        Resource contentResource = Mockito.mock(Resource.class);
+
+        ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+        Mockito.when(resolver.getResource("/etc/usergenerated/test")).thenReturn(contentResource);
+
+        ResourceResolverFactory factory = Mockito.mock(ResourceResolverFactory.class);
+        Mockito.when(factory.getServiceResourceResolver(Mockito.anyMap())).thenReturn(resolver);
+
+        DeleteUserGeneratedContentAction action = new DeleteUserGeneratedContentAction(factory);
+
+        ValueMap actionData = new ModifiableValueMapDecorator(new HashMap<String, Object>());
+        actionData.put("path", "/etc/usergenerated/test");
+
+        Resource actionResource = Mockito.mock(Resource.class);
+        Mockito.when(actionResource.getValueMap()).thenReturn(actionData);
+
+        FormRequest formRequest = Mockito.mock(FormRequest.class);
+        Mockito.when(formRequest.getFormData()).thenReturn(new ValueMapDecorator(Collections.emptyMap()));
+
+        try {
+            action.handleForm(actionResource, formRequest);
+            fail();
+        } catch (FormException fe) {
+            assertEquals("Failed to find UGC Parent", fe.getMessage());
+        }
+    }
+}
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/RequestPasswordResetActionTest.java b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/RequestPasswordResetActionTest.java
new file mode 100644
index 0000000..cd12d85
--- /dev/null
+++ b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/RequestPasswordResetActionTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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 static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collections;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.ValueFactory;
+import javax.jcr.ValueFormatException;
+
+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.jackrabbit.value.AbstractValueFactory;
+import org.apache.sling.api.resource.LoginException;
+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.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.servlethelpers.MockSlingHttpServletRequest;
+import org.apache.sling.testing.resourceresolver.MockResource;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class RequestPasswordResetActionTest {
+
+    private ResourceResolverFactory factory;
+    private ResourceResolver resolver;
+
+    @Before
+    public void init() throws FormException, LoginException, AccessDeniedException,
+            UnsupportedRepositoryOperationException, RepositoryException {
+
+        factory = Mockito.mock(ResourceResolverFactory.class);
+
+        resolver = Mockito.mock(ResourceResolver.class);
+        Mockito.when(factory.getServiceResourceResolver(Mockito.anyMap())).thenReturn(resolver);
+
+        JackrabbitSession session = Mockito.mock(JackrabbitSession.class);
+        Mockito.when(resolver.adaptTo(Mockito.any())).thenReturn(session);
+
+        ValueFactory vf = new AbstractValueFactory() {
+
+            @Override
+            protected void checkPathFormat(String pathValue) throws ValueFormatException {
+                // TODO Auto-generated method stub
+
+            }
+
+            @Override
+            protected void checkNameFormat(String nameValue) throws ValueFormatException {
+                // TODO Auto-generated method stub
+
+            }
+
+        };
+        Mockito.when(session.getValueFactory()).thenReturn(vf);
+
+        UserManager userManager = Mockito.mock(UserManager.class);
+        Mockito.when(session.getUserManager()).thenReturn(userManager);
+
+        Mockito.when(userManager.getAuthorizable("test@email.com")).thenReturn(Mockito.mock(User.class));
+
+    }
+
+    @Test
+    public void testHandleForm() throws FormException {
+
+        RequestPasswordResetAction action = new RequestPasswordResetAction(factory);
+
+        FormRequest request = new FormRequestImpl(new MockSlingHttpServletRequest(resolver), null, null);
+        request.getFormData().put("email", "test@email.com");
+
+        Resource actionResource = new MockResource("/content",
+                Collections.singletonMap(RequestPasswordResetAction.PN_RESETTIMEOUT, 2), null);
+
+        FormActionResult result = action.handleForm(actionResource, request);
+        assertTrue(result.isSucceeded());
+
+    }
+
+    @Test
+    public void testNoUser() throws FormException {
+
+        RequestPasswordResetAction action = new RequestPasswordResetAction(factory);
+
+        FormRequest request = new FormRequestImpl(new MockSlingHttpServletRequest(resolver), null, null);
+        request.getFormData().put("email", "test1@email.com");
+
+        Resource actionResource = new MockResource("/content",
+                Collections.singletonMap(RequestPasswordResetAction.PN_RESETTIMEOUT, 2), null);
+
+        FormActionResult result = action.handleForm(actionResource, request);
+        assertFalse(result.isSucceeded());
+
+    }
+
+    @Test
+    public void testHandles() throws FormException {
+
+        RequestPasswordResetAction action = new RequestPasswordResetAction(null);
+        Resource validResource = Mockito.mock(Resource.class);
+        Mockito.when(validResource.getResourceType()).thenReturn(RequestPasswordResetAction.RESOURCE_TYPE);
+        assertTrue(action.handles(validResource));
+
+        Resource inValidResource = Mockito.mock(Resource.class);
+        Mockito.when(inValidResource.getResourceType()).thenReturn("something/else");
+        assertFalse(action.handles(inValidResource));
+    }
+
+}
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/ResetPasswordActionTest.java b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/ResetPasswordActionTest.java
new file mode 100644
index 0000000..9db49ca
--- /dev/null
+++ b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/ResetPasswordActionTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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 static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Calendar;
+import java.util.Collections;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+import javax.jcr.ValueFormatException;
+
+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.jackrabbit.value.AbstractValueFactory;
+import org.apache.jackrabbit.value.DateValue;
+import org.apache.jackrabbit.value.StringValue;
+import org.apache.sling.api.resource.LoginException;
+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.cms.reference.forms.FormActionResult;
+import org.apache.sling.cms.reference.forms.FormConstants;
+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.servlethelpers.MockSlingHttpServletRequest;
+import org.apache.sling.testing.resourceresolver.MockResource;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class ResetPasswordActionTest {
+
+    private ResourceResolverFactory factory;
+    private ResourceResolver resolver;
+
+    @Before
+    public void init() throws FormException, LoginException, AccessDeniedException,
+            UnsupportedRepositoryOperationException, RepositoryException {
+
+        factory = Mockito.mock(ResourceResolverFactory.class);
+
+        resolver = Mockito.mock(ResourceResolver.class);
+        Mockito.when(factory.getServiceResourceResolver(Mockito.anyMap())).thenReturn(resolver);
+
+        JackrabbitSession session = Mockito.mock(JackrabbitSession.class);
+        Mockito.when(resolver.adaptTo(Mockito.any())).thenReturn(session);
+
+        ValueFactory vf = new AbstractValueFactory() {
+
+            @Override
+            protected void checkPathFormat(String pathValue) throws ValueFormatException {
+                // TODO Auto-generated method stub
+
+            }
+
+            @Override
+            protected void checkNameFormat(String nameValue) throws ValueFormatException {
+                // TODO Auto-generated method stub
+
+            }
+
+        };
+        Mockito.when(session.getValueFactory()).thenReturn(vf);
+
+        UserManager userManager = Mockito.mock(UserManager.class);
+        Mockito.when(session.getUserManager()).thenReturn(userManager);
+
+        User validUser = Mockito.mock(User.class);
+        Mockito.when(validUser.getProperty(FormConstants.PN_RESETTOKEN))
+                .thenReturn(new Value[] { new StringValue("123") });
+        Calendar cal = Calendar.getInstance();
+        cal.add(Calendar.HOUR, 24);
+        Mockito.when(validUser.getProperty(FormConstants.PN_RESETTIMEOUT))
+                .thenReturn(new Value[] { new DateValue(cal) });
+        Mockito.when(userManager.getAuthorizable("valid@email.com")).thenReturn(validUser);
+
+        User invalidUser = Mockito.mock(User.class);
+        Mockito.when(invalidUser.getProperty(FormConstants.PN_RESETTOKEN))
+                .thenReturn(new Value[] { new StringValue("456") });
+        Mockito.when(userManager.getAuthorizable("invalid@email.com")).thenReturn(invalidUser);
+
+        User expiredUser = Mockito.mock(User.class);
+        Mockito.when(expiredUser.getProperty(FormConstants.PN_RESETTOKEN))
+                .thenReturn(new Value[] { new StringValue("456") });
+        cal = Calendar.getInstance();
+        cal.add(Calendar.HOUR, -24);
+        Mockito.when(expiredUser.getProperty(FormConstants.PN_RESETTIMEOUT))
+                .thenReturn(new Value[] { new DateValue(cal) });
+        Mockito.when(userManager.getAuthorizable("expired@email.com")).thenReturn(expiredUser);
+
+    }
+
+    @Test
+    public void testHandleForm() throws FormException {
+
+        ResetPasswordAction action = new ResetPasswordAction(factory);
+
+        FormRequest request = new FormRequestImpl(new MockSlingHttpServletRequest(resolver), null, null);
+        request.getFormData().put("email", "valid@email.com");
+        request.getFormData().put(FormConstants.PN_RESETTOKEN, "123");
+        request.getFormData().put(FormConstants.PN_PASSWORD, "password1");
+
+        Resource actionResource = new MockResource("/content",
+                Collections.singletonMap(RequestPasswordResetAction.PN_RESETTIMEOUT, 1000000), null);
+
+        FormActionResult result = action.handleForm(actionResource, request);
+        assertTrue(result.isSucceeded());
+
+    }
+
+    public FormActionResult doReset(String email) throws FormException {
+
+        ResetPasswordAction action = new ResetPasswordAction(factory);
+
+        FormRequest request = new FormRequestImpl(new MockSlingHttpServletRequest(resolver), null, null);
+        request.getFormData().put("email", email);
+
+        Resource actionResource = new MockResource("/content",
+                Collections.singletonMap(RequestPasswordResetAction.PN_RESETTIMEOUT, 2), null);
+
+        return action.handleForm(actionResource, request);
+
+    }
+
+    @Test
+    public void testInvalid() throws FormException {
+
+        assertFalse(doReset("invalid@email.com").isSucceeded());
+        assertFalse(doReset("expired@email.com").isSucceeded());
+        assertFalse(doReset("test123@email.com").isSucceeded());
+
+    }
+
+    @Test
+    public void testHandles() throws FormException {
+        ResetPasswordAction action = new ResetPasswordAction(null);
+        Resource validResource = Mockito.mock(Resource.class);
+        Mockito.when(validResource.getResourceType()).thenReturn(ResetPasswordAction.RESOURCE_TYPE);
+        assertTrue(action.handles(validResource));
+
+        Resource inValidResource = Mockito.mock(Resource.class);
+        Mockito.when(inValidResource.getResourceType()).thenReturn("something/else");
+        assertFalse(action.handles(inValidResource));
+    }
+
+}
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/SendEmailActionTest.java b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/SendEmailActionTest.java
index eb4aaf7..66150e7 100644
--- a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/SendEmailActionTest.java
+++ b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/SendEmailActionTest.java
@@ -47,12 +47,9 @@ public class SendEmailActionTest {
         context.request().setResource(context.resourceResolver().getResource("/form/jcr:content/container/form"));
 
         resolver = context.resourceResolver();
-        action = new SendEmailAction();
-
         mailService = Mockito.mock(MailService.class);
         Mockito.when(mailService.getMessageBuilder()).thenReturn(new MockMessageBuilder());
-
-        action.setMailService(mailService);
+        action = new SendEmailAction(mailService);
     }
 
     @Test
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/UpdateProfileActionTest.java b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/UpdateProfileActionTest.java
new file mode 100644
index 0000000..af8d167
--- /dev/null
+++ b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/UpdateProfileActionTest.java
@@ -0,0 +1,165 @@
+/*
+ * 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 static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Calendar;
+import java.util.Collections;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.ValueFactory;
+import javax.jcr.ValueFormatException;
+
+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.jackrabbit.value.AbstractValueFactory;
+import org.apache.sling.api.resource.LoginException;
+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.api.resource.ResourceResolverFactory;
+import org.apache.sling.cms.reference.forms.FormActionResult;
+import org.apache.sling.cms.reference.forms.FormConstants;
+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.servlethelpers.MockSlingHttpServletRequest;
+import org.apache.sling.testing.resourceresolver.MockResource;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class UpdateProfileActionTest {
+
+    private ResourceResolverFactory factory;
+    private ResourceResolver resolver;
+    private User validUser;
+
+    @Before
+    public void init() throws FormException, LoginException, AccessDeniedException,
+            UnsupportedRepositoryOperationException, RepositoryException {
+
+        factory = Mockito.mock(ResourceResolverFactory.class);
+
+        resolver = Mockito.mock(ResourceResolver.class);
+
+        Mockito.when(factory.getServiceResourceResolver(Mockito.anyMap())).thenReturn(resolver);
+
+        JackrabbitSession session = Mockito.mock(JackrabbitSession.class);
+        Mockito.when(resolver.adaptTo(Mockito.any())).thenReturn(session);
+
+        ValueFactory vf = new AbstractValueFactory() {
+
+            @Override
+            protected void checkPathFormat(String pathValue) throws ValueFormatException {
+                // TODO Auto-generated method stub
+
+            }
+
+            @Override
+            protected void checkNameFormat(String nameValue) throws ValueFormatException {
+                // TODO Auto-generated method stub
+
+            }
+
+        };
+        Mockito.when(session.getValueFactory()).thenReturn(vf);
+
+        UserManager userManager = Mockito.mock(UserManager.class);
+        Mockito.when(session.getUserManager()).thenReturn(userManager);
+
+        validUser = Mockito.mock(User.class);
+        Mockito.when(userManager.getAuthorizable("valid@email.com")).thenReturn(validUser);
+
+    }
+
+    @Test
+    public void testHandleForm() throws FormException, RepositoryException {
+
+        UpdateProfileAction action = new UpdateProfileAction();
+
+        Mockito.when(resolver.getUserID()).thenReturn("valid@email.com");
+
+        FormRequest request = new FormRequestImpl(new MockSlingHttpServletRequest(resolver), null, null);
+        request.getFormData().put(FormConstants.PN_PASSWORD, "password1");
+        request.getFormData().put("dateField", Calendar.getInstance());
+        request.getFormData().put("doublefield", 2.0);
+        request.getFormData().put("intField", 2);
+        request.getFormData().put("arraufield", new String[] { "one", "two" });
+        request.getFormData().put(FormConstants.PN_PASSWORD, "password1");
+        request.getFormData().put(FormConstants.PN_PASSWORD, "password1");
+
+        Resource actionResource = new MockResource("/content",
+                Collections.singletonMap(RequestPasswordResetAction.PN_RESETTIMEOUT, 1000000), null);
+
+        FormActionResult result = action.handleForm(actionResource, request);
+        assertTrue(result.isSucceeded());
+    }
+
+    @Test
+    public void testMissingUser() throws FormException, RepositoryException {
+
+        UpdateProfileAction action = new UpdateProfileAction();
+
+        Mockito.when(resolver.getUserID()).thenReturn("invalid@email.com");
+
+        FormRequest request = new FormRequestImpl(new MockSlingHttpServletRequest(resolver), null, null);
+        request.getFormData().put(FormConstants.PN_PASSWORD, "password1");
+
+        Resource actionResource = new MockResource("/content",
+                Collections.singletonMap(RequestPasswordResetAction.PN_RESETTIMEOUT, 1000000), null);
+
+        FormActionResult result = action.handleForm(actionResource, request);
+        assertFalse(result.isSucceeded());
+    }
+
+    @Test
+    public void testError() throws FormException, RepositoryException, PersistenceException {
+
+        UpdateProfileAction action = new UpdateProfileAction();
+
+        Mockito.when(resolver.getUserID()).thenReturn("valid@email.com");
+        Mockito.doThrow(new PersistenceException("I'm a sad panda")).when(resolver).commit();
+
+        FormRequest request = new FormRequestImpl(new MockSlingHttpServletRequest(resolver), null, null);
+        request.getFormData().put(FormConstants.PN_PASSWORD, "password1");
+
+        Resource actionResource = new MockResource("/content",
+                Collections.singletonMap(RequestPasswordResetAction.PN_RESETTIMEOUT, 1000000), null);
+
+        FormActionResult result = action.handleForm(actionResource, request);
+        assertFalse(result.isSucceeded());
+    }
+
+    @Test
+    public void testHandles() throws FormException {
+        UpdateProfileAction action = new UpdateProfileAction();
+        Resource validResource = Mockito.mock(Resource.class);
+        Mockito.when(validResource.getResourceType()).thenReturn(UpdateProfileAction.RESOURCE_TYPE);
+        assertTrue(action.handles(validResource));
+
+        Resource inValidResource = Mockito.mock(Resource.class);
+        Mockito.when(inValidResource.getResourceType()).thenReturn("something/else");
+        assertFalse(action.handles(inValidResource));
+    }
+
+}
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/UpdateUserGeneratedContentActionTest.java b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/UpdateUserGeneratedContentActionTest.java
new file mode 100644
index 0000000..e2d59fb
--- /dev/null
+++ b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/UpdateUserGeneratedContentActionTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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 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.Collections;
+import java.util.HashMap;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.SlingHttpServletRequest;
+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.ValueMap;
+import org.apache.sling.api.wrappers.ModifiableValueMapDecorator;
+import org.apache.sling.api.wrappers.ValueMapDecorator;
+import org.apache.sling.cms.CMSConstants;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.apache.sling.cms.reference.forms.FormRequest;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class UpdateUserGeneratedContentActionTest {
+
+    @Test
+    public void testHandles() {
+
+        UpdateUserGeneratedContentAction ur = new UpdateUserGeneratedContentAction(null);
+
+        Resource validResource = Mockito.mock(Resource.class);
+        Mockito.when(validResource.getResourceType()).thenReturn("reference/components/forms/actions/updateugc");
+        assertTrue(ur.handles(validResource));
+
+        Resource invalidResource = Mockito.mock(Resource.class);
+        Mockito.when(invalidResource.getResourceType())
+                .thenReturn("reference/components/forms/actions/someotheraction");
+        assertFalse(ur.handles(invalidResource));
+    }
+
+    @Test
+    public void testInvalidServiceUser() throws FormException, LoginException {
+
+        LoginException le = new LoginException();
+        ResourceResolverFactory factory = Mockito.mock(ResourceResolverFactory.class);
+        Mockito.when(factory.getServiceResourceResolver(Mockito.anyMap())).thenThrow(le);
+
+        UpdateUserGeneratedContentAction ur = new UpdateUserGeneratedContentAction(factory);
+
+        ValueMap actionData = new ModifiableValueMapDecorator(new HashMap<String, Object>());
+        actionData.put("allowedProperties", new String[] { "name1", "name3", "name4" });
+        actionData.put("path", "/content/test");
+        actionData.put("serviceUser", "bob");
+
+        Resource actionResource = Mockito.mock(Resource.class);
+        Mockito.when(actionResource.getValueMap()).thenReturn(actionData);
+
+        FormRequest formRequest = Mockito.mock(FormRequest.class);
+        ValueMap formData = new ValueMapDecorator(new HashMap<>());
+        formData.put("name2", "value2");
+        formData.put("name3", "value3");
+        Mockito.when(formRequest.getFormData()).thenReturn(formData);
+
+        try {
+            ur.handleForm(actionResource, formRequest);
+            fail();
+        } catch (FormException fe) {
+            assertEquals(le, fe.getCause());
+        }
+    }
+
+    @Test
+    public void testHandleForm() throws FormException, LoginException {
+
+        ModifiableValueMap contentData = new ModifiableValueMapDecorator(new HashMap<String, Object>());
+        contentData.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+        Resource contentResource = Mockito.mock(Resource.class);
+        Mockito.when(contentResource.getValueMap()).thenReturn(contentData);
+        Mockito.when(contentResource.adaptTo(Mockito.any())).thenReturn(contentData);
+
+        Resource ugcResource = Mockito.mock(Resource.class);
+        Mockito.when(ugcResource.getResourceType()).thenReturn(CMSConstants.NT_UGC);
+        Mockito.when(ugcResource.getValueMap())
+                .thenReturn(new ValueMapDecorator(Collections.singletonMap("user", "auser")));
+        Mockito.when(contentResource.getParent()).thenReturn(ugcResource);
+
+        ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+        Mockito.when(resolver.getUserID()).thenReturn("auser");
+        SlingHttpServletRequest request = Mockito.mock(SlingHttpServletRequest.class);
+        Mockito.when(request.getResourceResolver()).thenReturn(resolver);
+        Mockito.when(resolver.getResource("/content/test")).thenReturn(contentResource);
+
+        ResourceResolverFactory factory = Mockito.mock(ResourceResolverFactory.class);
+        Mockito.when(factory.getServiceResourceResolver(Mockito.anyMap())).thenReturn(resolver);
+
+        UpdateUserGeneratedContentAction ur = new UpdateUserGeneratedContentAction(factory);
+
+        ValueMap actionData = new ModifiableValueMapDecorator(new HashMap<String, Object>());
+        actionData.put("allowedProperties", new String[] { "name1", "name3", "name4" });
+        actionData.put("path", "/content/test");
+        actionData.put("serviceUser", "bob");
+
+        Resource actionResource = Mockito.mock(Resource.class);
+        Mockito.when(actionResource.getValueMap()).thenReturn(actionData);
+
+        FormRequest formRequest = Mockito.mock(FormRequest.class);
+        Mockito.when(formRequest.getOriginalRequest()).thenReturn(request);
+
+        ValueMap formData = new ValueMapDecorator(new HashMap<>());
+        formData.put("name2", "value2");
+        formData.put("name3", "value3");
+        Mockito.when(formRequest.getFormData()).thenReturn(formData);
+
+        ur.handleForm(actionResource, formRequest);
+
+        assertEquals(3, contentResource.getValueMap().keySet().size());
+        assertEquals("value3", contentResource.getValueMap().get("name3"));
+
+    }
+
+    @Test
+    public void testNoParent() throws FormException, LoginException {
+
+        Resource contentResource = Mockito.mock(Resource.class);
+
+        ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+        Mockito.when(resolver.getResource("/etc/usergenerated/test")).thenReturn(contentResource);
+
+        ResourceResolverFactory factory = Mockito.mock(ResourceResolverFactory.class);
+        Mockito.when(factory.getServiceResourceResolver(Mockito.anyMap())).thenReturn(resolver);
+
+        UpdateUserGeneratedContentAction ur = new UpdateUserGeneratedContentAction(factory);
+
+        ValueMap actionData = new ModifiableValueMapDecorator(new HashMap<String, Object>());
+        actionData.put("path", "/etc/usergenerated/test");
+
+        Resource actionResource = Mockito.mock(Resource.class);
+        Mockito.when(actionResource.getValueMap()).thenReturn(actionData);
+
+        FormRequest formRequest = Mockito.mock(FormRequest.class);
+        ValueMap formData = new ValueMapDecorator(new HashMap<>());
+        formData.put("name2", "value2");
+        formData.put("name3", "value3");
+        Mockito.when(formRequest.getFormData()).thenReturn(formData);
+
+        try {
+            ur.handleForm(actionResource, formRequest);
+            fail();
+        } catch (FormException fe) {
+            assertEquals("Failed to find UGC Parent", fe.getMessage());
+        }
+    }
+}
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/UserGeneratedContentActionTest.java b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/UserGeneratedContentActionTest.java
new file mode 100644
index 0000000..065d19c
--- /dev/null
+++ b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/actions/UserGeneratedContentActionTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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 static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashMap;
+
+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.api.resource.ValueMap;
+import org.apache.sling.api.wrappers.ModifiableValueMapDecorator;
+import org.apache.sling.api.wrappers.ValueMapDecorator;
+import org.apache.sling.cms.NameFilter;
+import org.apache.sling.cms.reference.forms.FormException;
+import org.apache.sling.cms.reference.forms.FormRequest;
+import org.apache.sling.cms.usergenerated.UserGeneratedContentService;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class UserGeneratedContentActionTest {
+
+    @Test
+    public void testHandles() {
+
+        NameFilter filter = Mockito.mock(NameFilter.class);
+        UserGeneratedContentService ugcService = Mockito.mock(UserGeneratedContentService.class);
+
+        UserGeneratedContentAction ur = new UserGeneratedContentAction(filter, ugcService);
+
+        Resource validResource = Mockito.mock(Resource.class);
+        Mockito.when(validResource.getResourceType())
+                .thenReturn("reference/components/forms/actions/usergeneratedcontent");
+        assertTrue(ur.handles(validResource));
+
+        Resource invalidResource = Mockito.mock(Resource.class);
+        Mockito.when(invalidResource.getResourceType())
+                .thenReturn("reference/components/forms/actions/someotheraction");
+        assertFalse(ur.handles(invalidResource));
+    }
+
+    @Test
+    public void testActionResolver() throws FormException, PersistenceException {
+
+        NameFilter filter = Mockito.mock(NameFilter.class);
+        ResourceResolver resolver = Mockito.mock(ResourceResolver.class);
+
+        Resource container = Mockito.mock(Resource.class);
+        Mockito.when(container.getResourceResolver()).thenReturn(resolver);
+
+        UserGeneratedContentService ugcService = Mockito.mock(UserGeneratedContentService.class);
+        Mockito.when(ugcService.createUGCContainer(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()))
+                .thenReturn(container);
+
+        UserGeneratedContentAction ur = new UserGeneratedContentAction(filter, ugcService);
+
+        ValueMap actionData = new ModifiableValueMapDecorator(new HashMap<String, Object>());
+        actionData.put("additionalProperties", new String[] { "name1=value1", "name3=vale2", "name4" });
+        actionData.put("path", "/content/test");
+
+        Resource actionResource = Mockito.mock(Resource.class);
+        Mockito.when(actionResource.getValueMap()).thenReturn(actionData);
+        Mockito.when(actionResource.getResourceResolver()).thenReturn(resolver);
+
+        FormRequest formRequest = Mockito.mock(FormRequest.class);
+        ValueMap formData = new ValueMapDecorator(new HashMap<>());
+        formData.put("name2", "value2");
+        formData.put("name3", "value3");
+        Mockito.when(formRequest.getFormData()).thenReturn(formData);
+        ur.handleForm(actionResource, formRequest);
+        assertTrue(true);
+
+    }
+}
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/fields/SelectionHandlerTest.java b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/fields/HoneypotHandlerTest.java
similarity index 56%
copy from reference/src/test/java/org/apache/sling/cms/reference/forms/impl/fields/SelectionHandlerTest.java
copy to reference/src/test/java/org/apache/sling/cms/reference/forms/impl/fields/HoneypotHandlerTest.java
index 33baf32..260b315 100644
--- a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/fields/SelectionHandlerTest.java
+++ b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/fields/HoneypotHandlerTest.java
@@ -16,16 +16,14 @@
  */
 package org.apache.sling.cms.reference.forms.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.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
-import com.google.common.collect.ImmutableMap;
-
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.cms.reference.forms.FormException;
@@ -35,83 +33,64 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 
-public class SelectionHandlerTest {
+public class HoneypotHandlerTest {
 
     @Rule
     public final SlingContext context = new SlingContext();
-    private SelectionHandler handler;
+    private HoneypotHandler 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();
+        handler = new HoneypotHandler();
     }
 
     @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")));
+                .handles(resolver.getResource("/form/jcr:content/container/form/fields/fieldset/fields/honeypot")));
     }
 
     @Test
-    public void testSingleSelect() throws FormException {
+    public void testValid() throws FormException {
         ResourceResolver resolver = context.resourceResolver();
 
-        context.request()
-                .setParameterMap(ImmutableMap.<String, Object>builder().put("singleselect", "Hello World").build());
+        context.request().setParameterMap(Collections.singletonMap("someothervalue", "something else"));
 
         Map<String, Object> formData = new HashMap<>();
         Resource fieldResource = resolver
-                .getResource("/form/jcr:content/container/form/fields/fieldset/fields/singleselect");
+                .getResource("/form/jcr:content/container/form/fields/fieldset/fields/honeypot");
 
         handler.handleField(context.request(), fieldResource, formData);
 
-        assertEquals("Hello World", formData.get("singleselect"));
+        assertTrue(formData.isEmpty());
     }
 
     @Test
-    public void testMissingSingleSelect() throws FormException {
+    public void testInValid() throws FormException {
         ResourceResolver resolver = context.resourceResolver();
 
-        context.request().getParameterMap().put("singleselect", new String[] {});
+        context.request().setParameterMap(Collections.singletonMap("honeypot", "a value"));
 
         Map<String, Object> formData = new HashMap<>();
         Resource fieldResource = resolver
-                .getResource("/form/jcr:content/container/form/fields/fieldset/fields/singleselect");
+                .getResource("/form/jcr:content/container/form/fields/fieldset/fields/honeypot");
+
         try {
+
             handler.handleField(context.request(), fieldResource, formData);
             fail();
-        } catch (Exception e) {
+        } catch (FormException e) {
+            // expected
         }
-        assertFalse(formData.containsKey("singleselect"));
-    }
 
-    @Test
-    public void testMultipleSelect() throws FormException {
-        ResourceResolver resolver = context.resourceResolver();
-
-        context.request().setParameterMap(ImmutableMap.<String, Object>builder().put("multiselect", "").build());
-
-        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().setParameterMap(ImmutableMap.<String, Object>builder()
-                .put("multiselect", new String[] { "Thing 1", "Thing 2" }).build());
-        handler.handleField(context.request(), fieldResource, formData);
-        assertTrue(formData.containsKey("multiselect"));
+        assertTrue(formData.isEmpty());
     }
 
 }
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/fields/SelectionHandlerTest.java b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/fields/SelectionHandlerTest.java
index 33baf32..7f6eaa3 100644
--- a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/fields/SelectionHandlerTest.java
+++ b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/fields/SelectionHandlerTest.java
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import java.lang.annotation.Annotation;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -30,6 +31,7 @@ import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.cms.reference.forms.FormException;
 import org.apache.sling.cms.reference.forms.impl.SlingContextHelper;
+import org.apache.sling.cms.reference.forms.impl.fields.SelectionHandler.Config;
 import org.apache.sling.testing.mock.sling.junit.SlingContext;
 import org.junit.Before;
 import org.junit.Rule;
@@ -48,7 +50,20 @@ public class SelectionHandlerTest {
         context.request().setResource(context.resourceResolver().getResource("/form/jcr:content/container/form"));
 
         resolver = context.resourceResolver();
-        handler = new SelectionHandler();
+        handler = new SelectionHandler(new Config() {
+
+            @Override
+            public Class<? extends Annotation> annotationType() {
+                // TODO Auto-generated method stub
+                return null;
+            }
+
+            @Override
+            public String[] supportedTypes() {
+                return new String[] { SelectionHandler.DEFAULT_RESOURCE_TYPE };
+            }
+
+        });
     }
 
     @Test
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/fields/TextareaHandlerTest.java b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/fields/TextareaHandlerTest.java
index 3fb6f73..6b64af5 100644
--- a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/fields/TextareaHandlerTest.java
+++ b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/fields/TextareaHandlerTest.java
@@ -21,20 +21,22 @@ import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import java.lang.annotation.Annotation;
 import java.util.HashMap;
 import java.util.Map;
 
+import com.google.common.collect.ImmutableMap;
+
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.cms.reference.forms.FormException;
 import org.apache.sling.cms.reference.forms.impl.SlingContextHelper;
+import org.apache.sling.cms.reference.forms.impl.fields.TextareaHandler.Config;
 import org.apache.sling.testing.mock.sling.junit.SlingContext;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 
-import com.google.common.collect.ImmutableMap;
-
 public class TextareaHandlerTest {
 
     @Rule
@@ -48,7 +50,19 @@ public class TextareaHandlerTest {
         context.request().setResource(context.resourceResolver().getResource("/form/jcr:content/container/form"));
 
         resolver = context.resourceResolver();
-        handler = new TextareaHandler();
+        handler = new TextareaHandler(new Config() {
+
+            @Override
+            public Class<? extends Annotation> annotationType() {
+                return null;
+            }
+
+            @Override
+            public String[] supportedTypes() {
+                return new String[] { TextareaHandler.DEFAULT_RESOURCE_TYPE };
+            }
+
+        });
     }
 
     @Test
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/fields/TextfieldHandlerTest.java b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/fields/TextfieldHandlerTest.java
index 3f1afcd..9d85b95 100644
--- a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/fields/TextfieldHandlerTest.java
+++ b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/fields/TextfieldHandlerTest.java
@@ -18,10 +18,14 @@ package org.apache.sling.cms.reference.forms.impl.fields;
 
 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 static org.junit.Assert.fail;
 
+import java.io.ByteArrayInputStream;
+import java.lang.annotation.Annotation;
 import java.util.Calendar;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -31,6 +35,7 @@ import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.cms.reference.forms.FormException;
 import org.apache.sling.cms.reference.forms.impl.SlingContextHelper;
+import org.apache.sling.cms.reference.forms.impl.fields.TextfieldHandler.Config;
 import org.apache.sling.testing.mock.sling.junit.SlingContext;
 import org.junit.Before;
 import org.junit.Rule;
@@ -49,7 +54,19 @@ public class TextfieldHandlerTest {
         context.request().setResource(context.resourceResolver().getResource("/form/jcr:content/container/form"));
 
         resolver = context.resourceResolver();
-        handler = new TextfieldHandler();
+        handler = new TextfieldHandler(new Config() {
+
+            @Override
+            public Class<? extends Annotation> annotationType() {
+                return null;
+            }
+
+            @Override
+            public String[] supportedTypes() {
+                return new String[] { TextfieldHandler.DEFAULT_RESOURCE_TYPE };
+            }
+
+        });
     }
 
     @Test
@@ -71,12 +88,21 @@ public class TextfieldHandlerTest {
             handler.handleField(context.request(), fieldResource, formData);
             fail();
         } catch (FormException pe) {
+            // expected
+        }
+
+        context.request()
+                .setParameterMap(ImmutableMap.<String, Object>builder().put("invaliddate", "2019-99-99").build());
+
+        Resource invalidType = resolver.getResource("/form/jcr:content/container/invalidtype");
+        try {
+            handler.handleField(context.request(), invalidType, formData);
+            fail();
+        } catch (FormException pe) {
 
         }
 
         Resource invalidDate = resolver.getResource("/form/jcr:content/container/invaliddate");
-        context.request()
-                .setParameterMap(ImmutableMap.<String, Object>builder().put("invalidate", "2019-02-12").build());
         try {
             handler.handleField(context.request(), invalidDate, formData);
             fail();
@@ -103,9 +129,30 @@ public class TextfieldHandlerTest {
             handler.handleField(context.request(), fieldResource, formData);
             fail();
         } catch (FormException pe) {
+            // expected
+        }
 
+        fieldResource = resolver
+                .getResource("/form/jcr:content/container/form/fields/fieldset/fields/unvalidateddouble");
+        try {
+            handler.handleField(context.request(), fieldResource, formData);
+            fail();
+        } catch (FormException pe) {
+            // expected
         }
+    }
+
+    @Test
+    public void testFile() throws FormException {
+        ResourceResolver resolver = context.resourceResolver();
+
+        context.request().setParameterMap(Collections.singletonMap("file", new ByteArrayInputStream(new byte[0])));
 
+        Map<String, Object> formData = new HashMap<>();
+        Resource fieldResource = resolver.getResource("/form/jcr:content/container/form/fields/fieldset/fields/file");
+        handler.handleField(context.request(), fieldResource, formData);
+        assertNotNull(formData.get("file"));
+        assertEquals(ByteArrayInputStream.class, formData.get("file").getClass());
     }
 
     @Test
@@ -137,9 +184,17 @@ public class TextfieldHandlerTest {
             handler.handleField(context.request(), fieldResource, formData);
             fail();
         } catch (FormException pe) {
-
+            // expected
         }
 
+        fieldResource = resolver
+                .getResource("/form/jcr:content/container/form/fields/fieldset/fields/unvalidatedinteger");
+        try {
+            handler.handleField(context.request(), fieldResource, formData);
+            fail();
+        } catch (FormException pe) {
+
+        }
     }
 
     @Test
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/providers/RequestParametersValueProviderTest.java b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/providers/RequestParametersValueProviderTest.java
new file mode 100644
index 0000000..eeb771c
--- /dev/null
+++ b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/providers/RequestParametersValueProviderTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.servlethelpers.MockSlingHttpServletRequest;
+import org.apache.sling.testing.resourceresolver.MockResource;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class RequestParametersValueProviderTest {
+
+    @Test
+    public void testAccepts() {
+        RequestParametersValueProvider rpp = new RequestParametersValueProvider();
+
+        Resource validResource = Mockito.mock(Resource.class);
+        Mockito.when(validResource.getResourceType())
+                .thenReturn("reference/components/forms/providers/requestparameters");
+        assertTrue(rpp.handles(validResource));
+
+        Resource invalidResource = Mockito.mock(Resource.class);
+        Mockito.when(invalidResource.getResourceType())
+                .thenReturn("reference/components/forms/providers/someotherprovider");
+        assertFalse(rpp.handles(invalidResource));
+    }
+
+    @Test
+    public void testRequestParameters() {
+        RequestParametersValueProvider rpp = new RequestParametersValueProvider();
+
+        MockResource mockResource = new MockResource("/apps/rpv",
+                Collections.singletonMap("allowedParameters", new String[] { "name1", "name3", "name4" }), null);
+
+        MockSlingHttpServletRequest mockRequest = new MockSlingHttpServletRequest(null);
+        mockRequest.addRequestParameter("name1", "value1");
+        mockRequest.addRequestParameter("name2", "value2");
+        mockRequest.addRequestParameter("name4", "value1");
+        mockRequest.addRequestParameter("name4", "value2");
+
+        Map<String, Object> formData = new HashMap<>();
+        rpp.loadValues(mockRequest, mockResource, formData);
+
+        assertTrue(formData.containsKey("name1"));
+        assertEquals("value1", formData.get("name1"));
+
+        assertFalse(formData.containsKey("name2"));
+        assertFalse(formData.containsKey("name3"));
+
+        assertTrue(formData.containsKey("name1"));
+        assertTrue(Arrays.equals(new String[] { "value1", "value2" }, (String[]) formData.get("name4")));
+    }
+}
diff --git a/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/providers/SuffixResourceFormValueProviderTest.java b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/providers/SuffixResourceFormValueProviderTest.java
new file mode 100644
index 0000000..ab61a41
--- /dev/null
+++ b/reference/src/test/java/org/apache/sling/cms/reference/forms/impl/providers/SuffixResourceFormValueProviderTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.request.RequestPathInfo;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.testing.resourceresolver.MockResource;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class SuffixResourceFormValueProviderTest {
+
+    public Resource providerResource;
+
+    @Before
+    public void init() {
+        Map<String, Object> properties = new HashMap<>();
+        properties.put("basePath", "/content/apath");
+        properties.put("allowedProperties", new String[] { "name1", "name3", "name4" });
+
+        providerResource = new MockResource("/apps/rpv", properties, null);
+
+    }
+
+    @Test
+    public void testAccepts() {
+        SuffixResourceFormValueProvider sr = new SuffixResourceFormValueProvider();
+
+        Resource validResource = Mockito.mock(Resource.class);
+        Mockito.when(validResource.getResourceType()).thenReturn("reference/components/forms/providers/suffixresource");
+        assertTrue(sr.handles(validResource));
+
+        Resource invalidResource = Mockito.mock(Resource.class);
+        Mockito.when(invalidResource.getResourceType())
+                .thenReturn("reference/components/forms/providers/someotherprovider");
+        assertFalse(sr.handles(invalidResource));
+    }
+
+    @Test
+    public void testNoSuffix() {
+        SuffixResourceFormValueProvider sr = new SuffixResourceFormValueProvider();
+
+        Map<String, Object> formData = new HashMap<>();
+
+        SlingHttpServletRequest mockRequest = Mockito.mock(SlingHttpServletRequest.class);
+        RequestPathInfo rpi = Mockito.mock(RequestPathInfo.class);
+        Mockito.when(mockRequest.getRequestPathInfo()).thenReturn(rpi);
+        sr.loadValues(mockRequest, providerResource, formData);
+
+        assertEquals(0, formData.entrySet().size());
+        Mockito.verify(rpi).getSuffixResource();
+
+    }
+
+    @Test
+    public void testValidSuffix() {
+        SuffixResourceFormValueProvider sr = new SuffixResourceFormValueProvider();
+
+        Map<String, Object> formData = new HashMap<>();
+
+        Map<String, Object> properties = new HashMap<>();
+        properties.put("name1", "value1");
+        properties.put("name2", "value2");
+
+        Resource suffixResource = new MockResource("/content/apath/apage", properties, null);
+
+        SlingHttpServletRequest mockRequest = Mockito.mock(SlingHttpServletRequest.class);
+        RequestPathInfo rpi = Mockito.mock(RequestPathInfo.class);
+        Mockito.when(rpi.getSuffixResource()).thenReturn(suffixResource);
+        Mockito.when(mockRequest.getRequestPathInfo()).thenReturn(rpi);
+        sr.loadValues(mockRequest, providerResource, formData);
+
+        assertEquals(2, formData.entrySet().size());
+        assertEquals("value1", formData.get("name1"));
+        assertEquals("/content/apath/apage", formData.get("suffixResource"));
+        Mockito.verify(rpi).getSuffixResource();
+
+    }
+
+    @Test
+    public void testAllAllowed() {
+        SuffixResourceFormValueProvider sr = new SuffixResourceFormValueProvider();
+
+        Map<String, Object> formData = new HashMap<>();
+
+        Map<String, Object> properties = new HashMap<>();
+        properties.put("name1", "value1");
+        properties.put("name2", "value2");
+
+        Map<String, Object> providerProperties = new HashMap<>();
+        providerProperties.put("basePath", "/content/apath");
+
+        Resource pr2 = new MockResource("/apps/rpv", providerProperties, null);
+
+        Resource suffixResource = new MockResource("/content/apath/apage", properties, null);
+
+        SlingHttpServletRequest mockRequest = Mockito.mock(SlingHttpServletRequest.class);
+        RequestPathInfo rpi = Mockito.mock(RequestPathInfo.class);
+        Mockito.when(rpi.getSuffixResource()).thenReturn(suffixResource);
+        Mockito.when(mockRequest.getRequestPathInfo()).thenReturn(rpi);
+        sr.loadValues(mockRequest, pr2, formData);
+
+        assertEquals(3, formData.entrySet().size());
+        assertEquals("value1", formData.get("name1"));
+        assertEquals("value2", formData.get("name2"));
+        assertEquals("/content/apath/apage", formData.get("suffixResource"));
+        Mockito.verify(rpi).getSuffixResource();
+
+    }
+
+    @Test
+    public void testInvalidSuffix() {
+        SuffixResourceFormValueProvider sr = new SuffixResourceFormValueProvider();
+
+        Map<String, Object> formData = new HashMap<>();
+
+        Map<String, Object> properties = new HashMap<>();
+        properties.put("name1", "value1");
+        properties.put("name2", "value2");
+
+        Resource suffixResource = new MockResource("/content/anotherpath", properties, null);
+
+        SlingHttpServletRequest mockRequest = Mockito.mock(SlingHttpServletRequest.class);
+        RequestPathInfo rpi = Mockito.mock(RequestPathInfo.class);
+        Mockito.when(rpi.getSuffixResource()).thenReturn(suffixResource);
+        Mockito.when(mockRequest.getRequestPathInfo()).thenReturn(rpi);
+        sr.loadValues(mockRequest, providerResource, formData);
+
+        assertEquals(0, formData.entrySet().size());
+        Mockito.verify(rpi).getSuffixResource();
+
+    }
+}
diff --git a/reference/src/test/resources/form.json b/reference/src/test/resources/form.json
index dbe3595..2021c1a 100644
--- a/reference/src/test/resources/form.json
+++ b/reference/src/test/resources/form.json
@@ -8,11 +8,18 @@
         "published": true,
         "container": {
             "jcr:primaryType": "nt:unstructured",
+            "invalidtype": {
+                "jcr:primaryType": "nt:unstructured",
+                "required": true,
+                "name": "invalidtype",
+                "label": "Daty",
+                "saveAs": "date",
+                "sling:resourceType": "reference/components/forms/fields/textfield"
+            },
             "invaliddate": {
                 "jcr:primaryType": "nt:unstructured",
                 "required": true,
-                "name": "datefield",
-                "type": "text",
+                "name": "invaliddate",
                 "label": "Daty",
                 "saveAs": "date",
                 "sling:resourceType": "reference/components/forms/fields/textfield"
@@ -101,6 +108,22 @@
                                 "saveAs": "double",
                                 "sling:resourceType": "reference/components/forms/fields/textfield"
                             },
+                            "unvalidateddouble": {
+                                "jcr:primaryType": "nt:unstructured",
+                                "required": true,
+                                "name": "double",
+                                "type": "text",
+                                "label": "Daty",
+                                "saveAs": "double",
+                                "sling:resourceType": "reference/components/forms/fields/textfield"
+                            },
+                            "file": {
+                                "jcr:primaryType": "nt:unstructured",
+                                "name": "file",
+                                "type": "file",
+                                "label": "File",
+                                "sling:resourceType": "reference/components/forms/fields/textfield"
+                            },
                             "integer": {
                                 "jcr:primaryType": "nt:unstructured",
                                 "required": true,
@@ -110,6 +133,15 @@
                                 "saveAs": "integer",
                                 "sling:resourceType": "reference/components/forms/fields/textfield"
                             },
+                            "unvalidatedinteger": {
+                                "jcr:primaryType": "nt:unstructured",
+                                "required": true,
+                                "name": "integer",
+                                "type": "text",
+                                "label": "Inty",
+                                "saveAs": "integer",
+                                "sling:resourceType": "reference/components/forms/fields/textfield"
+                            },
                             "datefield": {
                                 "jcr:primaryType": "nt:unstructured",
                                 "required": true,
@@ -130,6 +162,11 @@
                                 "multiple": false,
                                 "sling:resourceType": "reference/components/forms/fields/selection",
                                 "display": "select"
+                            },
+                            "honeypot": {
+                                "jcr:primaryType": "nt:unstructured",
+                                "name": "honeypot",
+                                "sling:resourceType": "reference/components/forms/fields/honeypot"
                             }
                         }
                     }
diff --git a/transformer/src/test/resources/thumbnail.png b/transformer/src/test/resources/thumbnail.png
index 62cf067..f38c3a4 100644
Binary files a/transformer/src/test/resources/thumbnail.png and b/transformer/src/test/resources/thumbnail.png differ