Package org.openfaces.component.validation

Source Code of org.openfaces.component.validation.ValidationSupportResponseWriter

/*
* OpenFaces - JSF Component Library 2.0
* Copyright (C) 2007-2012, TeamDev Ltd.
* licensing@openfaces.org
* Unless agreed in writing the contents of this file are subject to
* the GNU Lesser General Public License Version 2.1 (the "LGPL" License).
* This library 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.
* Please visit http://openfaces.org/licensing/ for more details.
*/
package org.openfaces.component.validation;

import org.openfaces.renderkit.validation.BaseMessageRenderer;
import org.openfaces.renderkit.validation.ValidatorUtil;
import org.openfaces.util.Components;
import org.openfaces.util.Converters;
import org.openfaces.util.Log;
import org.openfaces.util.RawScript;
import org.openfaces.util.Rendering;
import org.openfaces.util.Resources;
import org.openfaces.validator.ClientValidatorUtil;

import javax.faces.application.FacesMessage;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
import javax.faces.component.UIForm;
import javax.faces.component.UIMessage;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.context.ResponseWriterWrapper;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* @author Pavel Kaplin
*/
public class ValidationSupportResponseWriter extends ResponseWriterWrapper {
    private static final String[] ELEMENTS_ALLOWED_RENDER_AFTER_THEIR_START = new String[]{
            "td", "tr", "table", "colgroup", "select", "textarea", "option", "script", "optgroup", "map", "fieldset", "frameset"
    };
    private static final String[] ELEMENTS_ALLOW_RENDERER_AFTER_THEIR_END = new String[]{
            "tr", "td", "th", "col", "colgroup", "caption", "thead", "tfoot", "tbody", "legend",
            "optgroup", "area", "frame"
    };
    private static final String[] ELEMENTS_ALLOWED_FOR_ID = new String[]{
            "input", "textarea", "select"
    };

    private ResponseWriter wrapped;
    private StringWriter validationScriptWriter;
    private List<String> validationScripts;
    private Set<String> formsHaveOnSubmitRendered;
    private boolean clientIdRendered;
    private String clientId;
    private boolean processingValidation;
    private String element;
    private ValidationProcessor validationProcessor;

    public ValidationSupportResponseWriter(ResponseWriter writer) {
        this.wrapped = writer;
        formsHaveOnSubmitRendered = new HashSet<String>();
    }

    @Override
    protected ResponseWriter getWrapped() {
        return wrapped;
    }

    public ValidationSupportResponseWriter(ResponseWriter writer, ValidationSupportResponseWriter parent) {
        this.wrapped = writer;
        validationScriptWriter = parent.validationScriptWriter;
        validationScripts = parent.validationScripts;
        formsHaveOnSubmitRendered = parent.formsHaveOnSubmitRendered;
        processingValidation = parent.processingValidation;
        element = parent.element;
    }

    public void write(char cbuf[], int off, int len) throws IOException {
        flushValidationScriptInElement();
        wrapped.write(cbuf, off, len);
    }

    public void flush() throws IOException {
        flushValidationScriptInElement();
        wrapped.flush();
    }

    public void close() throws IOException {
        flushValidationScriptInElement();
        wrapped.close();
    }

    public void endDocument() throws IOException {
        flushValidationScriptInElement();
        wrapped.endDocument();
    }

    public void startElement(String name, UIComponent component) throws IOException {
        /* IMPORTANT: THIS METHOD AND ALL ITS CALLEES IS A PERFORMANCE BOTTLENECK.
           MODIFY WITH CARE. ENSURE MINIMAL EXECUTION TIME AND AMOUNT OF OUTPUT */
        flushValidationScriptInElement();
        FacesContext context = FacesContext.getCurrentInstance();
        if (!processingValidation && isValidationNeeded(context, component)) {
            ValidationProcessor processor = getValidationProcessor(context);
            if (processor != null) {
                boolean needProcessGlobalMessages = !processor.isGlobalMessagesProcessed();
                boolean needProcessEditableValueHolder = component instanceof EditableValueHolder && component.isRendered();
                if (needProcessGlobalMessages || needProcessEditableValueHolder) {
                    processingValidation = true;

                    if (validationScriptWriter == null)
                        validationScriptWriter = new StringWriter();

                    ResponseWriter responseWriter = context.getResponseWriter();
                    ResponseWriter clonedResponseWriter = cloneWithWriter(validationScriptWriter);
                    context.setResponseWriter(clonedResponseWriter);
                    List<String> prevRenderedJsLinks = substituteRenderedJsLinks(context);

                    if (needProcessGlobalMessages)
                        processGlobalMessages(context, processor);

                    if (needProcessEditableValueHolder)
                        processEditableValueHolder(context, component, processor);

                    context.setResponseWriter(responseWriter);
                    restoreRenderedJsLinks(context, prevRenderedJsLinks);

                    processingValidation = false;
                }
            }
        }
        wrapped.startElement(name, component);
        element = name;
    }

    public void endElement(String name) throws IOException {
        renderClientIdIfNecessary(true);
        wrapped.endElement(name);
        element = null;
        flushValidationScriptAfterEnd(name);
    }

    public void writeAttribute(String name, Object value, String property) throws IOException {
        wrapped.writeAttribute(name, value, property);
        if ("id".equals(name) && clientId != null && clientId.equals(value)) {
            clientId = null;
            clientIdRendered = true;
        }
    }

    public void writeURIAttribute(String name, Object value, String property) throws IOException {
        wrapped.writeURIAttribute(name, value, property);
        if ("id".equals(name) && clientId != null && clientId.equals(value)) {
            clientId = null;
            clientIdRendered = true;
        }
    }

    public void writeComment(Object comment) throws IOException {
        flushValidationScriptInElement();
        wrapped.writeComment(comment);
    }

    public void writeText(Object text, String property) throws IOException {
        flushValidationScriptInElement();
        wrapped.writeText(text, property);
    }

    public void writeText(char[] text, int off, int len) throws IOException {
        flushValidationScriptInElement();
        wrapped.writeText(text, off, len);
    }

    public ResponseWriter cloneWithWriter(Writer writer) {
        return new ValidationSupportResponseWriter(this.wrapped.cloneWithWriter(writer), this);
    }

    private void flushValidationScriptInElement() throws IOException {
        renderClientIdIfNecessary(false);
        if (isAllowedToRenderAfterElementStart(element)) {
            flushValidationScriptIfNecessary();
        }
    }

    private void flushValidationScriptAfterEnd(String element) throws IOException {
        if (isAllowedToRenderAfterElementEnd(element)) {
            flushValidationScriptIfNecessary();
        }
    }

    private void flushValidationScriptIfNecessary() throws IOException {
        if (isValidationScriptNotEmpty() && clientIdRendered) {
            String validationScript = validationScriptWriter.toString();
            putNewJSLinksInRenderedJsLinks();
            wrapped.write(validationScript);
            validationScriptWriter = null;

            validationScripts = null;
        }
    }

    private void renderClientIdIfNecessary(boolean endingElement) throws IOException {
        if (element != null && clientId != null) {
            if (!clientIdRendered &&
                isValidationScriptNotEmpty() &&
                !processingValidation &&
                isElementAllowedForId(element)) {
                writeAttribute("id", clientId, null);
                clientId = null;
                clientIdRendered = true;
            } else if (endingElement) {
                clientId = null;
            }
        }
    }

    private boolean isValidationScriptNotEmpty() {
        return validationScriptWriter != null && validationScriptWriter.getBuffer().length() > 0;
    }

    private ValidationProcessor getValidationProcessor(FacesContext context) {
        if (validationProcessor == null)
            validationProcessor = ValidationProcessor.getInstance(context);
        return validationProcessor;
    }

    private boolean isValidationNeeded(FacesContext context, UIComponent component) {
        ValidationProcessor processor = getValidationProcessor(context);
        return processor != null && !processor.isGlobalMessagesProcessed() || (component instanceof EditableValueHolder);
    }

    private void processEditableValueHolder(FacesContext context, UIComponent component, ValidationProcessor processor) throws IOException {
        EditableValueHolder editableValueHolder = (EditableValueHolder) component;
        VerifiableComponent[] verifiableComponents = processor.getVerifiableComponents(context);
        VerifiableComponent vc = VerifiableComponent.getVerifiableComponent(
                verifiableComponents, editableValueHolder, component.getClientId(context));
        UIForm parentForm = Components.getEnclosingForm(component);
        if (parentForm == null) {
            String componentClass = component.getClass().getName();
            if (componentClass.equals("org.richfaces.component.html.HtmlModalPanel") ||
                componentClass.equals("org.richfaces.component.html.HtmlTabPanel")) {
                // RichFaces TabPanel and ModalPanel implement EditableValueHolder though it can normally be out of form
                return;
            }
            Log.log(context, "Warn: enclosing form cannot be found for component " + component.getClientId(context) + ". Client-side validation will not be available for it.");
            return; // org.richfaces.component.html.HtmlModalPanel
        }
        if (vc != null) {
            clientId = component.getClientId(context);
            clientIdRendered = false;
            vc.setParentForm(parentForm);
            vc.addValidators(editableValueHolder.getValidators());
            vc.setRequired(editableValueHolder.isRequired());
            vc.setConverter(Converters.getConverter(component, context));
            vc.addMessageFromContext(context);
            vc.updateClientValidatorsScriptsAndLibraries(context, processor);
            processor.addVerifiableComponent(vc);
            handleEditableValueHolder(context, vc, processor);
        }
    }

    private void processGlobalMessages(FacesContext context, ValidationProcessor processor) throws IOException {
        Iterator<FacesMessage> globalMessages = context.getMessages(null);
        if (globalMessages.hasNext()) {
            Resources.renderJSLinkIfNeeded(context, Resources.utilJsURL(context));
            Resources.renderJSLinkIfNeeded(context, ValidatorUtil.getValidatorUtilJsUrl(context));
            while (globalMessages.hasNext()) {
                FacesMessage message = globalMessages.next();
                Rendering.renderInitScript(context, ClientValidatorUtil.getScriptAddGlobalMessage(message));
            }
        }
        processor.confirmGlobalMessagesProcessing();
    }

    private void handleEditableValueHolder(FacesContext context, VerifiableComponent vc, ValidationProcessor vp) throws IOException {
        ClientValidationMode clientValidationRuleForComponent = vp.getClientValidationRuleForComponent(vc);
        // if client validation switched at least for one validatable component - render client script for component validation
        StringBuilder commonScript = vc.getCommonScript();
        if (commonScript == null || commonScript.length() == 0) return;

        // if component does not have defined presentation in page and default client validation presentation switched on
        // then render default validation presentation for component (currently floating icon message)
        UIForm parentForm = vc.getParentForm();
        if (vp.isUseDefaultClientValidationPresentationForForm(parentForm) || vp.isUseDefaultServerValidationPresentationForForm(parentForm)) {
            int bubbleIndex = nextBubbleIndex(context);
            addPresentationComponent(vc.getComponent(), vc.getParentForm(), bubbleIndex, vp);
        }
        if (!vp.getClientValidationRuleForComponent(vc).equals(ClientValidationMode.OFF)) {
            List<String> javascriptLibraries = vc.getJavascriptLibrariesUrls();
            String[] javascriptLibrariesArray = javascriptLibraries.toArray(new String[javascriptLibraries.size()]);
            Rendering.renderInitScript(context, new RawScript(commonScript.toString()), javascriptLibrariesArray);
        }

        if (clientValidationRuleForComponent.equals(ClientValidationMode.ON_SUBMIT)) {
            String formClientId = parentForm.getClientId(context);
            if (!formsHaveOnSubmitRendered.contains(formClientId)) {
                Rendering.renderInitScript(context,
                        new RawScript("O$.addOnSubmitEvent(O$._autoValidateForm,'" + formClientId + "');\n"),
                        ValidatorUtil.getValidatorUtilJsUrl(context));
                formsHaveOnSubmitRendered.add(formClientId);
            }
        } else if (clientValidationRuleForComponent.equals(ClientValidationMode.ON_DEMAND)) {
            Rendering.renderInitScript(context,
                    new RawScript("O$.addNotValidatedInput('" + vc.getClientId() + "');"),
                    ValidatorUtil.getValidatorUtilJsUrl(context));
        }
    }

    public static void resetBubbleIndex(FacesContext context) {
        Map<String, Object> sessionMap = context.getExternalContext().getSessionMap();
        sessionMap.remove(getBubbleIndexKey());
    }

    private static int nextBubbleIndex(FacesContext context) {
        Map<String, Object> sessionMap = context.getExternalContext().getSessionMap();
        String key = getBubbleIndexKey();
        Integer bubbleIndex = (Integer) sessionMap.get(key);
        if (bubbleIndex == null) bubbleIndex = 0;
        sessionMap.put(key, bubbleIndex + 1);
        return bubbleIndex;
    }

    private static String getBubbleIndexKey() {
        return ValidationSupportResponseWriter.class.getName() + ".bubbleIndex";
    }

    private static void addPresentationComponent(UIComponent component, UIForm parentForm, int idx, ValidationProcessor vp) throws IOException {
        ClientValidationSupport clientValidationSupport = vp.getClientValidationSupport(parentForm);
        createPresentationComponent("dfm" + idx, component, clientValidationSupport, vp);
    }

    private static void createPresentationComponent(String id, UIComponent component, ClientValidationSupport support, ValidationProcessor vp) throws IOException {
        FacesContext context = FacesContext.getCurrentInstance();
        UIMessage defaultMessage = vp.getDefaultPresentationInstance(context, support);

        if (defaultMessage instanceof FloatingIconMessage) {
            createFloatingIconMessage(context, id, component, (FloatingIconMessage) defaultMessage);
        } else {
            throw new IllegalStateException("Illegal default presentation component type. Expected FloatingIconMessage, actual " + defaultMessage.getClass());
        }
    }

    private static FloatingIconMessage createFloatingIconMessage(FacesContext context,
                                                          String id,
                                                          UIComponent component,
                                                          FloatingIconMessage template) throws IOException {
        FloatingIconMessage message = new FloatingIconMessage(template, true);
        message.setId(id);
        message.setFor(":" + component.getClientId(context));
        message.getAttributes().put(BaseMessageRenderer.DEFAULT_PRESENTATION, Boolean.TRUE);
        message.encodeBegin(context);
        message.encodeChildren(context);
        message.encodeEnd(context);
        return message;
    }

    private boolean isAllowedToRenderAfterElementStart(String element) {
        return element != null && !isStringInArray(element, ELEMENTS_ALLOWED_RENDER_AFTER_THEIR_START);

    }

    private boolean isAllowedToRenderAfterElementEnd(String element) {
        return !isStringInArray(element, ELEMENTS_ALLOW_RENDERER_AFTER_THEIR_END);
    }

    private static boolean isElementAllowedForId(String element) {
        return isStringInArray(element, ELEMENTS_ALLOWED_FOR_ID);
    }

    private static boolean isStringInArray(String str, String[] array) {
        str = str.toLowerCase();
        for (String s : array) {
            if (s.equals(str))
                return true;
        }
        return false;
    }

    private List<String> substituteRenderedJsLinks(FacesContext context) {
        if (validationScripts == null) {
            validationScripts = new ArrayList<String>();
        }
        Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
        List<String> renderedJsLinks = (List<String>) requestMap.get(Resources.RENDERED_JS_LINKS);
        if (renderedJsLinks != null) {
            for (String js : renderedJsLinks) {
                if (!validationScripts.contains(js))
                    validationScripts.add(js);
            }
        }
        requestMap.put(Resources.RENDERED_JS_LINKS, validationScripts);
        requestMap.put(Resources.POSTPONE_JS_LINK_RENDERING, Boolean.TRUE);
        return renderedJsLinks;
    }

    private void restoreRenderedJsLinks(FacesContext context, List<String> prevRenderedJsLinks) {
        Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
        requestMap.put(Resources.RENDERED_JS_LINKS, prevRenderedJsLinks);
        requestMap.put(Resources.POSTPONE_JS_LINK_RENDERING, Boolean.FALSE);
    }

    private void putNewJSLinksInRenderedJsLinks() throws IOException {
        if (validationScripts == null)
            return;
        FacesContext context = FacesContext.getCurrentInstance();
        ResponseWriter prevWriter = context.getResponseWriter();
        try {
            // use original writer when writing js links to avoid recursive startElement...putNewJSLinksInRenderedJsLinks
            // call on this ValidationSupportResponseWriter
            context.setResponseWriter(wrapped);

            List<String> renderedJsLinks = Resources.getRenderedJsLinks(context);
            for (String js : validationScripts) {
                if (!renderedJsLinks.contains(js))
                    Resources.renderJSLinkIfNeeded(context, js);
            }
        } finally {
            context.setResponseWriter(prevWriter);
        }
    }
}
TOP

Related Classes of org.openfaces.component.validation.ValidationSupportResponseWriter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.