ru.runa.wf.web.FormPresentationUtils.java Source code

Java tutorial

Introduction

Here is the source code for ru.runa.wf.web.FormPresentationUtils.java

Source

/*
 * This file is part of the RUNA WFE project.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; version 2.1
 * of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 */

package ru.runa.wf.web;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.jsp.PageContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import ru.runa.common.WebResources;
import ru.runa.common.web.Commons;
import ru.runa.common.web.HTMLUtils;
import ru.runa.common.web.MessagesException;
import ru.runa.common.web.Resources;
import ru.runa.common.web.form.IdForm;
import ru.runa.wf.web.action.LoadProcessDefinitionHtmlFileAction;
import ru.runa.wf.web.form.DefinitionFileForm;
import ru.runa.wfe.commons.web.PortletUrlType;
import ru.runa.wfe.var.VariableProvider;

import com.google.common.base.Charsets;
import com.google.common.base.Throwables;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

/**
 * 
 * Created on 21.02.2007
 * 
 */
public class FormPresentationUtils {
    private static final Log log = LogFactory.getLog(FormPresentationUtils.class);

    private static final String PROTOCOL_SEPARATOR = "//";
    /**
     * This map contains Tag name -> Href attribute name
     */
    private static final Map<String, String> TAG_NAME_ATTRIBUTE_MAP = new HashMap<String, String>();
    static {
        TAG_NAME_ATTRIBUTE_MAP.put("a", "href");
        TAG_NAME_ATTRIBUTE_MAP.put("img", "src");
        TAG_NAME_ATTRIBUTE_MAP.put("frame", "src");
        TAG_NAME_ATTRIBUTE_MAP.put("link", "href");
        TAG_NAME_ATTRIBUTE_MAP.put("script", "src");
    }
    private static final String NAME_ATTR = "name";
    private static final String TYPE_ATTR = "type";
    private static final String CSS_CLASS_ATTR = "class";
    private static final String ERROR_CONTAINER = "div";

    public static String adjustUrls(PageContext pageContext, Long definitionId, String htmlHref,
            byte[] originalBytes) {
        Document document = HTMLUtils.readHtml(originalBytes);
        adjustUrls(pageContext, document, definitionId, htmlHref);
        return HTMLUtils.writeHtml(document);
    }

    private static void adjustUrls(PageContext pageContext, Document document, Long definitionId, String htmlHref) {
        if (pageContext != null) {
            for (Map.Entry<String, String> tagNameTagAttributeEntry : TAG_NAME_ATTRIBUTE_MAP.entrySet()) {
                String tagName = tagNameTagAttributeEntry.getKey();
                NodeList htmlTagElements = document.getElementsByTagName(tagName);
                if (htmlTagElements.getLength() > 0) {
                    String attributeName = tagNameTagAttributeEntry.getValue();
                    handleElements(htmlTagElements, attributeName, htmlHref, pageContext, definitionId);
                }
            }
            if (WebResources.getBooleanProperty("form.tr.title.clean", true)) {
                NodeList trElements = document.getElementsByTagName("tr");
                for (int i = 0; i < trElements.getLength(); i++) {
                    Node node = trElements.item(i);
                    if (node.getAttributes().getNamedItem("title") != null) {
                        node.getAttributes().removeNamedItem("title");
                    }
                }
            }
        }
    }

    private static void handleElements(NodeList nodeList, String hrefAttributeName, String htmlHref,
            PageContext pageContext, Long id) {
        String[] fileNameStructureElements = htmlHref.split("/");
        Map<String, Object> params = Maps.newHashMap();
        params.put(IdForm.ID_INPUT_NAME, id);

        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            Node hrefNode = node.getAttributes().getNamedItem(hrefAttributeName);
            if (hrefNode == null) {
                continue;
            }
            String href = hrefNode.getNodeValue();
            if (href == null || href.length() == 0 || href.startsWith("#") || href.startsWith("javascript")) {
                continue;
            }
            if (isHrefRelative(href)) {
                String url = getNormalizedUrlForElement(htmlHref, fileNameStructureElements, href);
                int anchorIndex = url.indexOf("#");
                String anchor = null;
                if (anchorIndex >= 0) {
                    anchor = url.substring(anchorIndex + 1);
                    url = url.substring(0, anchorIndex);
                }
                params.put(DefinitionFileForm.URL_INPUT_NAME, url);
                String newHref = Commons.getActionUrl(LoadProcessDefinitionHtmlFileAction.ACTION_PATH, params,
                        anchor, pageContext, PortletUrlType.Resource);
                hrefNode.setNodeValue(newHref);
            }
        }
    }

    private static String getNormalizedUrlForElement(String htmlHref, String[] fileNameStructureElements,
            String originalHref) {
        StringBuilder normalizedUrl = new StringBuilder();

        if (originalHref.startsWith("./")) {
            originalHref = originalHref.substring(2);
        }

        if (originalHref.startsWith("../")) {
            int prefixLength = "../".length();
            int counter = 0;
            while (originalHref.startsWith("../", counter * prefixLength)) {
                counter++;
            }
            if (counter < fileNameStructureElements.length) {
                for (int j = 0; j < fileNameStructureElements.length - counter - 1; j++) {
                    normalizedUrl.append(fileNameStructureElements[j]);
                    normalizedUrl.append("/");
                }
            } else {
                normalizedUrl.append("#");
            }
        } else if (originalHref.startsWith("/")) {
            // DO NOTHING
        } else if (originalHref.startsWith("#")) {
            normalizedUrl.append(htmlHref);
        } else {
            int lastDirIndex = htmlHref.lastIndexOf("/");
            if (lastDirIndex > 0) {
                String additionalPath = htmlHref.substring(0, lastDirIndex + 1);
                normalizedUrl.append(additionalPath);
            }
        }
        return normalizedUrl.append(originalHref).toString();
    }

    private static boolean isHrefRelative(String href) {
        if (href.contains(PROTOCOL_SEPARATOR) || href.startsWith("/")) {
            return false;
        } else {
            return true;
        }
    }

    public static String adjustForm(PageContext pageContext, Long definitionId, String formHtml,
            VariableProvider variableProvider, List<String> requiredVarNames) {
        try {
            Map<String, String> userErrors = FormSubmissionUtils.getUserInputErrors(pageContext.getRequest());
            Document document = HTMLUtils.readHtml(formHtml.getBytes(Charsets.UTF_8));
            adjustUrls(pageContext, document, definitionId, "form.ftl");
            NodeList htmlTagElements = document.getElementsByTagName("input");
            for (int i = 0; i < htmlTagElements.getLength(); i++) {
                Element node = (Element) htmlTagElements.item(i);
                String typeName = node.getAttribute(TYPE_ATTR);
                String inputName = node.getAttribute(NAME_ATTR);
                if (WebResources.isHighlightRequiredFields() && requiredVarNames.contains(inputName)) {
                    Element requiredNode = node;
                    if ("file".equalsIgnoreCase(typeName) && WebResources.isAjaxFileInputEnabled()) {
                        requiredNode = (Element) node.getParentNode();
                    }
                    addRequiredClassAttribute(requiredNode);
                }
                handleErrors(userErrors, inputName, pageContext, document, node);
            }
            NodeList textareaElements = document.getElementsByTagName("textarea");
            for (int i = 0; i < textareaElements.getLength(); i++) {
                Element node = (Element) textareaElements.item(i);
                String inputName = node.getAttribute(NAME_ATTR);
                if (WebResources.isHighlightRequiredFields() && requiredVarNames.contains(inputName)) {
                    addRequiredClassAttribute(node);
                }
                handleErrors(userErrors, inputName, pageContext, document, node);
            }
            NodeList selectElements = document.getElementsByTagName("select");
            for (int i = 0; i < selectElements.getLength(); i++) {
                Element node = (Element) selectElements.item(i);
                String inputName = node.getAttribute(NAME_ATTR);
                if (WebResources.isHighlightRequiredFields() && requiredVarNames.contains(inputName)) {
                    wrapSelectToErrorContainer(document, node, inputName, true);
                }
                handleErrors(userErrors, inputName, pageContext, document, node);
            }
            if (!userErrors.isEmpty()) {
                Set<String> messages = Sets.newHashSet();
                for (String variableName : userErrors.keySet()) {
                    messages.add(variableName + ": " + getErrorText(pageContext, userErrors, variableName));
                }
                log.debug("Not for all errors inputs found, appending errors to the end of the form: " + messages);
                document.getLastChild().appendChild(document.createElement("hr"));
                Element font = document.createElement("font");
                for (String message : messages) {
                    font.appendChild(document.createElement("br"));
                    font.setAttribute(CSS_CLASS_ATTR, "error");
                    Element b = document.createElement("b");
                    b.appendChild(document.createTextNode(message));
                    font.appendChild(b);
                }
                document.getLastChild().appendChild(font);
            }
            return HTMLUtils.writeHtml(document);
        } catch (Exception e) {
            throw Throwables.propagate(e);
        }
    }

    private static String getErrorText(PageContext pageContext, Map<String, String> errors, String inputName) {
        String errorText = errors.get(inputName);
        if (errorText == null) {
            errorText = Commons.getMessage(MessagesException.MESSAGE_VARIABLE_FORMAT_ERROR.getKey(), pageContext,
                    new Object[] { inputName });
        }
        if (errorText.trim().length() == 0) {
            errorText = Commons.getMessage(MessagesException.MESSAGE_VALIDATION_ERROR.getKey(), pageContext);
        }
        return errorText;
    }

    private static void handleErrors(Map<String, String> errors, String inputName, PageContext pageContext,
            Document document, Element node) {
        if (errors.containsKey(inputName)) {
            String errorText = getErrorText(pageContext, errors, inputName);
            if ("file".equalsIgnoreCase(node.getAttribute(TYPE_ATTR)) && WebResources.isAjaxFileInputEnabled()) {
                try {
                    node = (Element) ((Element) ((Element) node.getParentNode()).getParentNode()).getParentNode();
                } catch (Exception e) {
                    log.error("Unexpected file input format", e);
                }
            }
            if (WebResources.useImagesForValidationErrors()) {
                Element errorImg = document.createElement("img");
                errorImg.setAttribute("title", errorText);
                errorImg.setAttribute("src",
                        Commons.getUrl("/images/error.gif", pageContext, PortletUrlType.Resource));
                Node parent = node.getParentNode();
                parent.insertBefore(errorImg, node.getNextSibling());
            } else {
                node.setAttribute("title", errorText);
                if ("select".equalsIgnoreCase(node.getTagName())) {
                    node = wrapSelectToErrorContainer(document, node, inputName, false);
                }
                addClassAttribute(node, Resources.CLASS_INVALID);
            }
            // avoiding multiple error labels
            errors.remove(inputName);
        }
    }

    private static void addRequiredClassAttribute(Element element) {
        addClassAttribute(element, Resources.CLASS_REQUIRED);
    }

    private static void addClassAttribute(Element element, String cssClass) {
        String cssClasses = element.getAttribute(CSS_CLASS_ATTR) + " " + cssClass;
        element.setAttribute(CSS_CLASS_ATTR, cssClasses);
    }

    private static Element wrapSelectToErrorContainer(Document document, Node selectNode, String selectName,
            boolean required) {
        Node parentNode = selectNode.getParentNode();
        if (parentNode instanceof Element && ERROR_CONTAINER.equalsIgnoreCase(parentNode.getNodeName())) {
            return (Element) parentNode;
        }
        log.debug("Wrapping select[name='" + selectName + "'] to " + ERROR_CONTAINER);
        parentNode.removeChild(selectNode);
        Element div = document.createElement(ERROR_CONTAINER);
        if (required) {
            addClassAttribute(div, "requiredWrapper");
        }
        div.appendChild(selectNode);
        parentNode.appendChild(div);
        return div;
    }
}