/* * Copyright 2017 OmniFaces * * 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. */ package org.omnifaces.component.input.componentidparam; import java.io.IOException; import java.io.Writer; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; import javax.faces.context.ResponseWriterWrapper; import org.omnifaces.component.input.ComponentIdParam; /** * ResponseWriter intended to work in conjunction with the {@link ComponentIdParam} component. * <p> * This allows rendering to proceed to the output if the current component matches any of the given ids, otherwise simply does not send anything to * the output. * * @since 1.1 * @author Arjan Tijms * */ public class ConditionalResponseWriter extends ResponseWriterWrapper { private final ResponseWriter responseWriter; private final FacesContext facesContext; private final List<String> componentIds; private final List<String> clientIds; private final boolean renderChildren; private UIComponent lastComponent; private boolean lastRendered; private Map<String, Boolean> renderedIdCache = new HashMap<>(); private Map<UIComponent, Boolean> renderedReferenceCache = new HashMap<>(); public ConditionalResponseWriter(ResponseWriter responseWriter, FacesContext facesContext, List<String> componentIds, List<String> clientIds, boolean renderChildren) { this.responseWriter = responseWriter; this.facesContext = facesContext; this.componentIds = componentIds; this.clientIds = clientIds; this.renderChildren = renderChildren; } // ResponseWriter overrides that do some kind of writing @Override public void endCDATA() throws IOException { if (isForRenderedComponent()) { super.endCDATA(); } } @Override public void endElement(String name) throws IOException { if (isForRenderedComponent()) { super.endElement(name); } } @Override public void endDocument() throws IOException { if (isForRenderedComponent()) { super.endDocument(); } } @Override public void startCDATA() throws IOException { if (isForRenderedComponent()) { super.startCDATA(); } } @Override public void startDocument() throws IOException { if (isForRenderedComponent()) { super.startDocument(); } } @Override public void startElement(String name, UIComponent component) throws IOException { if (isForRenderedComponent()) { super.startElement(name, component); } } @Override public void write(char[] cbuf, int off, int len) throws IOException { if (isForRenderedComponent()) { super.write(cbuf, off, len); } } @Override public void writeAttribute(String name, Object value, String property) throws IOException { if (isForRenderedComponent()) { super.writeAttribute(name, value, property); } } @Override public void writeComment(Object comment) throws IOException { if (isForRenderedComponent()) { super.writeComment(comment); } } @Override public void writeText(char[] text, int off, int len) throws IOException { if (isForRenderedComponent()) { super.writeText(text, off, len); } } @Override public void writeText(Object text, String property) throws IOException { if (isForRenderedComponent()) { super.writeText(text, property); } } @Override public void writeText(Object text, UIComponent component, String property) throws IOException { if (isForRenderedComponent()) { super.writeText(text, component, property); } } @Override public void writeURIAttribute(String name, Object value, String property) throws IOException { if (isForRenderedComponent()) { super.writeURIAttribute(name, value, property); } } // Writer overrides @Override public Writer append(char c) throws IOException { if (isForRenderedComponent()) { return super.append(c); } return this; } @Override public Writer append(CharSequence csq) throws IOException { if (isForRenderedComponent()) { return super.append(csq); } return this; } @Override public Writer append(CharSequence csq, int start, int end) throws IOException { if (isForRenderedComponent()) { return super.append(csq, start, end); } return this; } @Override public void write(char[] cbuf) throws IOException { if (isForRenderedComponent()) { super.write(cbuf); } } @Override public void write(int c) throws IOException { if (isForRenderedComponent()) { super.write(c); } } @Override public void write(String str) throws IOException { if (isForRenderedComponent()) { super.write(str); } } @Override public void write(String str, int off, int len) throws IOException { if (isForRenderedComponent()) { super.write(str, off, len); } } private boolean isForRenderedComponent() { UIComponent currentComponent = UIComponent.getCurrentComponent(facesContext); // Typically a single component writes multiple times to the response writer in quick succession. // Shortcut id matching by doing a cheaper check on the outcome of the last processed component. if (lastComponent == currentComponent) { return lastRendered; } lastComponent = currentComponent; // Check if a rendering decision already made for this component by checking the cache if (renderedIdCache.containsKey(currentComponent.getClientId())) { lastRendered = renderedIdCache.get(currentComponent.getClientId()); return lastRendered; } // No decision made, check for an explicit id match lastRendered = componentIds.contains(currentComponent.getId()) || clientIds.contains(currentComponent.getClientId()); if (renderChildren) { checkParents(currentComponent); } return lastRendered; } private void checkParents(UIComponent component) { // If current component not rendered because of explicit id match, check if parent is rendered. if (!lastRendered) { boolean found = false; for (UIComponent parent = component.getParent(); parent != null; parent = parent.getParent()) { if (renderedIdCache.containsKey(parent.getClientId())) { lastRendered = renderedIdCache.get(parent.getClientId()); found = true; } else if (renderedReferenceCache.containsKey(parent)) { lastRendered = renderedReferenceCache.get(parent); found = true; } if (found) { break; } } } else { // Explicitly rendered component, remember this by reference, since client-id can change even for components // that aren't in an iterating naming container (e.g. UIData changes its own client-id during iteration) renderedReferenceCache.put(component, lastRendered); } // Also remember client-id, in addition to the component reference since iterating is often implemented by swapping the state and identity // from the same component instance. So components with the same object identity can have different component identities. renderedIdCache.put(component.getClientId(), lastRendered); } @Override public ResponseWriter getWrapped() { return responseWriter; } }