com.adobe.acs.commons.forms.helpers.impl.PostFormHelperImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.adobe.acs.commons.forms.helpers.impl.PostFormHelperImpl.java

Source

/*
 * #%L
 * ACS AEM Commons Bundle
 * %%
 * Copyright (C) 2013 Adobe
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package com.adobe.acs.commons.forms.helpers.impl;

import com.adobe.acs.commons.forms.Form;
import com.adobe.acs.commons.forms.helpers.FormHelper;
import com.adobe.acs.commons.forms.helpers.PostFormHelper;
import com.adobe.acs.commons.util.PathInfoUtil;
import com.adobe.granite.xss.XSSAPI;
import com.day.cq.wcm.api.Page;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.felix.scr.annotations.*;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.request.RequestParameter;
import org.apache.sling.api.request.RequestParameterMap;
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.commons.json.JSONException;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;

@Component(label = "ACS AEM Commons - Abstract POST Form Helper", description = "Abstract Form Helper. Do not use directly; instead use the PostRedirectGetFormHelper or ForwardAsGetFormHelper.", enabled = true, metatype = false, immediate = false)
@Service(value = PostFormHelper.class)
public class PostFormHelperImpl implements PostFormHelper {
    private static final Logger log = LoggerFactory.getLogger(PostFormHelperImpl.class);
    static final String[] FORM_INPUTS = { FORM_NAME_INPUT, FORM_RESOURCE_INPUT };

    @Reference
    protected ResourceResolverFactory resourceResolverFactory;

    @Reference
    protected XSSAPI xssApi;

    /**
     * OSGi Properties *
     */
    private static final String DEFAULT_SUFFIX = "/submit/form";
    private String suffix = DEFAULT_SUFFIX;
    @Property(label = "Suffix", description = "Forward-as-GET Request Suffix used to identify Forward-as-GET POST Request", value = DEFAULT_SUFFIX)
    private static final String PROP_SUFFIX = "prop.form-suffix";

    @Override
    public Form getForm(String formName, SlingHttpServletRequest request) {
        throw new UnsupportedOperationException(
                "Do not call AbstractFormHelper.getForm(..) direct. This is an abstract service.");
    }

    @Override
    public String getFormInputsHTML(final Form form, final String... keys) {
        // The form objects data and errors should be xssProtected before being passed into this method
        StringBuffer html = new StringBuffer();

        html.append("<input type=\"hidden\" name=\"").append(FORM_NAME_INPUT).append("\" value=\"")
                .append(xssApi.encodeForHTMLAttr(form.getName())).append("\"/>\n");

        final String resourcePath = form.getResourcePath();
        html.append("<input type=\"hidden\" name=\"").append(FORM_RESOURCE_INPUT).append("\" value=\"")
                .append(xssApi.encodeForHTMLAttr(resourcePath)).append("\"/>\n");

        for (final String key : keys) {
            if (form.has(key)) {
                html.append("<input type=\"hidden\" name=\"").append(key).append("\" value=\"")
                        .append(form.get(key)).append("\"/>\n");
            }
        }

        return html.toString();
    }

    @Override
    public String getAction(final String path) {
        return getAction(path, null);
    }

    @Override
    public String getAction(final String path, final String formSelector) {
        String actionPath = path;

        ResourceResolver adminResourceResolver = null;
        try {
            adminResourceResolver = resourceResolverFactory.getAdministrativeResourceResolver(null);
            actionPath = adminResourceResolver.map(path);
        } catch (LoginException e) {
            log.error("Could not attain an admin ResourceResolver to map the Form's Action URI");
            // Use the unmapped ActionPath
        } finally {
            if (adminResourceResolver != null && adminResourceResolver.isLive()) {
                adminResourceResolver.close();
            }
        }

        actionPath += FormHelper.EXTENSION + this.getSuffix();
        if (StringUtils.isNotBlank(formSelector)) {
            actionPath += "/" + formSelector;
        }

        return actionPath;
    }

    @Override
    public void renderForm(Form form, String path, SlingHttpServletRequest request,
            SlingHttpServletResponse response) throws IOException, ServletException, JSONException {
        throw new UnsupportedOperationException("Use a specific Forms implementation helper.");
    }

    @Override
    public void renderForm(Form form, Page page, SlingHttpServletRequest request, SlingHttpServletResponse response)
            throws IOException, ServletException, JSONException {
        throw new UnsupportedOperationException("Use a specific Forms implementation helper.");
    }

    @Override
    public void renderForm(Form form, Resource resource, SlingHttpServletRequest request,
            SlingHttpServletResponse response) throws IOException, ServletException, JSONException {
        throw new UnsupportedOperationException("Use a specific Forms implementation helper.");
    }

    @Override
    public void renderOtherForm(Form form, String path, String selectors, SlingHttpServletRequest request,
            SlingHttpServletResponse response) throws IOException, ServletException, JSONException {
        throw new UnsupportedOperationException("Use a specific Forms implementation helper.");
    }

    @Override
    public void renderOtherForm(Form form, Page page, String selectors, SlingHttpServletRequest request,
            SlingHttpServletResponse response) throws IOException, ServletException, JSONException {
        throw new UnsupportedOperationException("Use a specific Forms implementation helper.");
    }

    @Override
    public void renderOtherForm(Form form, Resource resource, String selectors, SlingHttpServletRequest request,
            SlingHttpServletResponse response) throws IOException, ServletException, JSONException {
        throw new UnsupportedOperationException("Use a specific Forms implementation helper.");
    }

    @Override
    public String getAction(final Resource resource) {
        return getAction(resource.getPath());
    }

    @Override
    public String getAction(final Resource resource, final String formSelector) {
        return getAction(resource.getPath(), formSelector);
    }

    @Override
    public String getAction(final Page page) {
        return getAction(page.getPath());
    }

    @Override
    public String getAction(final Page page, final String formSelector) {
        return getAction(page.getPath(), formSelector);
    }

    @Override
    public String getSuffix() {
        return this.suffix;
    }

    /**
     * Determines of this FormHelper should handle the POST request
     *
     * @param formName
     * @param request
     * @return
     */
    protected boolean doHandlePost(final String formName, final SlingHttpServletRequest request) {
        if (StringUtils.equalsIgnoreCase("POST", request.getMethod())) {
            // Form should have a hidden input with the name this.getLookupKey(..) and value formName
            return StringUtils.equals(formName, request.getParameter(this.getPostLookupKey(formName)));
        } else {
            return false;
        }
    }

    /**
     * Gets the Form from POST requests
     *
     * @param formName
     * @param request
     * @return
     */
    protected Form getPostForm(final String formName, final SlingHttpServletRequest request) {
        final Map<String, String> map = new HashMap<String, String>();

        final RequestParameterMap requestMap = request.getRequestParameterMap();

        for (final String key : requestMap.keySet()) {
            // POST LookupKey formName param does not matter
            if (StringUtils.equals(key, this.getPostLookupKey(null))) {
                continue;
            }

            final RequestParameter[] values = requestMap.getValues(key);

            if (values == null || values.length == 0) {
                log.debug("Value did not exist for key: {}", key);
            } else if (values.length == 1) {
                log.debug("Adding to form data: {} ~> {}", key, values[0].toString());
                map.put(key, values[0].getString());
            } else {
                // TODO: Handle multi-value parameter values; Requires support for transporting them and re-writing them back into HTML Form on error
                for (final RequestParameter value : values) {
                    // Use the first non-blank value, or use the last value (which will be blank or not-blank)
                    final String tmp = value.toString();
                    map.put(key, tmp);

                    if (StringUtils.isNotBlank(tmp)) {
                        break;
                    }
                }
            }
        }

        return this.clean(new Form(formName, request.getResource().getPath(), map));
    }

    /**
     * Gets the Key used to look up the form during handling of POST requests
     *
     * @param formName
     * @return
     */
    protected String getPostLookupKey(final String formName) {
        // This may change; keeping as method call to ease future refactoring
        return FORM_NAME_INPUT;
    }

    /**
     * Removes unused Map entries from the provided map
     *
     * @param form
     * @return
     */
    protected Form clean(final Form form) {
        final Map<String, String> map = form.getData();
        final Map<String, String> cleanedMap = new HashMap<String, String>();

        for (final Map.Entry<String, String> entry : map.entrySet()) {
            if (!ArrayUtils.contains(FORM_INPUTS, entry.getKey()) && StringUtils.isNotBlank(entry.getValue())) {
                cleanedMap.put(entry.getKey(), entry.getValue());
            }
        }

        return new Form(form.getName(), form.getResourcePath(), cleanedMap, form.getErrors());
    }

    /**
     * Protect a Form in is entirety (data and errors)
     *
     * @param form
     * @return
     */
    protected Form getProtectedForm(final Form form) {
        return new Form(form.getName(), form.getResourcePath(), this.getProtectedData(form.getData()),
                this.getProtectedErrors(form.getErrors()));
    }

    /**
     * Protect a Map representing Form Data
     *
     * @param data
     * @return
     */
    protected Map<String, String> getProtectedData(final Map<String, String> data) {
        final Map<String, String> protectedData = new HashMap<String, String>();

        // Protect data for HTML Attributes
        for (final Map.Entry<String, String> entry : data.entrySet()) {
            protectedData.put(entry.getKey(), xssApi.encodeForHTMLAttr(entry.getValue()));
        }

        return protectedData;
    }

    /**
     * Protect a Map representing Form Errors
     *
     * @param errors
     * @return
     */
    protected Map<String, String> getProtectedErrors(final Map<String, String> errors) {
        final Map<String, String> protectedErrors = new HashMap<String, String>();

        // Protect data for HTML
        for (final Map.Entry<String, String> entry : errors.entrySet()) {
            protectedErrors.put(entry.getKey(), xssApi.encodeForHTML(entry.getValue()));
        }

        return protectedErrors;
    }

    public boolean hasValidSuffix(final SlingHttpServletRequest slingRequest) {
        final String requestSuffix = slingRequest.getRequestPathInfo().getSuffix();
        if (StringUtils.equals(requestSuffix, this.getSuffix())
                || StringUtils.startsWith(requestSuffix, this.getSuffix() + "/")) {
            return true;
        }

        return false;
    }

    /**
     * Gets the Form Selector for the form POST request
     *
     * @param slingRequest
     * @return
     */
    public String getFormSelector(final SlingHttpServletRequest slingRequest) {
        final String requestSuffix = slingRequest.getRequestPathInfo().getSuffix();
        if (StringUtils.equals(requestSuffix, this.getSuffix())
                || !StringUtils.startsWith(requestSuffix, this.getSuffix() + "/")) {
            return null;
        }

        final int segments = StringUtils.split(this.getSuffix(), '/').length;
        if (segments < 1) {
            return null;
        }

        final String formSelector = PathInfoUtil.getSuffixSegment(slingRequest, segments);
        return StringUtils.stripToNull(formSelector);
    }

    /**
     * Encodes URL data
     *
     * @param unencoded
     * @return
     */
    protected String encode(String unencoded) {
        if (StringUtils.isBlank(unencoded)) {
            return "";
        }

        try {
            return java.net.URLEncoder.encode(unencoded, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            return unencoded;
        }
    }

    /**
     * Decodes URL data
     *
     * @param encoded
     * @return
     */
    protected String decode(String encoded) {
        if (StringUtils.isBlank(encoded)) {
            return "";
        }

        try {
            return java.net.URLDecoder.decode((encoded), "UTF-8");
        } catch (UnsupportedEncodingException e) {
            return encoded;
        }
    }

    @Activate
    protected void activate(final Map<String, String> properties) {
        this.suffix = PropertiesUtil.toString(properties.get(PROP_SUFFIX), DEFAULT_SUFFIX);
        if (StringUtils.isBlank(this.suffix)) {
            // No whitespace please
            this.suffix = DEFAULT_SUFFIX;
        }
    }
}