/*
* 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.output;
import java.io.IOException;
import java.io.StringWriter;
import javax.el.ValueExpression;
import javax.faces.component.FacesComponent;
import javax.faces.component.html.HtmlOutputFormat;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import org.omnifaces.util.State;
/**
* <p>
* The <code><o:outputFormat></code> is a component that extends the standard <code><h:outputFormat></code>
* with support for capturing the output and exposing it into the request scope by the variable name as specified by the
* <code>var</code> attribute.
* <p>
* You can use it the same way as <code><h:outputFormat></code>, you only need to change <code>h:</code> into
* <code>o:</code> to get the extra support for <code>var</code> attribute. Here's are some usage examples:
* <pre>
* <o:outputFormat value="#{i18n['link.title']}" var="_link_title">
* <f:param value="#{bean.foo}" />
* <f:param value="#{bean.bar}" />
* </o:outputFormat>
* <h:commandLink value="#{i18n['link.value']}" title="#{_link_title}" />
* </pre>
* <pre>
* <o:outputFormat value="#{bean.number}" var="_percentage">
* <f:convertNumber type="percent" />
* </o:outputFormat>
* <div title="Percentage: #{_percentage}" />
* </pre>
* <p>
* Make sure that the <code>var</code> attribute value doesn't conflict with any of existing variable names in the
* current EL scope, such as managed bean names. It would be a good naming convention to start their names with
* <code>_</code>.
*
* @author Bauke Scholtz
* @since 1.2
*/
@FacesComponent(OutputFormat.COMPONENT_TYPE)
public class OutputFormat extends HtmlOutputFormat {
// Public constants -----------------------------------------------------------------------------------------------
/** The standard component type. */
public static final String COMPONENT_TYPE = "org.omnifaces.component.output.OutputFormat";
// Private constants ----------------------------------------------------------------------------------------------
private static final String ERROR_EXPRESSION_DISALLOWED =
"A value expression is disallowed on 'var' attribute of OutputFormat.";
private enum PropertyKeys {
// Cannot be uppercased. They have to exactly match the attribute names.
var;
}
// Variables ------------------------------------------------------------------------------------------------------
private final State state = new State(getStateHelper());
// Actions --------------------------------------------------------------------------------------------------------
/**
* An override which checks if this isn't been invoked on <code>var</code> attribute.
* Finally it delegates to the super method.
* @throws IllegalArgumentException When this value expression is been set on <code>var</code> attribute.
*/
@Override
public void setValueExpression(String name, ValueExpression binding) {
if (PropertyKeys.var.toString().equals(name)) {
throw new IllegalArgumentException(ERROR_EXPRESSION_DISALLOWED);
}
super.setValueExpression(name, binding);
}
/**
* If the <code>var</code> attribute is set, start capturing the output.
*/
@Override
public void encodeBegin(FacesContext context) throws IOException {
if (getVar() != null) {
ResponseWriter originalResponseWriter = context.getResponseWriter();
StringWriter buffer = new StringWriter();
context.setResponseWriter(originalResponseWriter.cloneWithWriter(buffer));
context.getAttributes().put(this + "_writer", originalResponseWriter);
context.getAttributes().put(this + "_buffer", buffer);
}
super.encodeBegin(context);
}
/**
* If the <code>var</code> attribute is set, stop capturing the output and expose it in request scope by the
* <code>var</code> attribute value as variable name.
*/
@Override
public void encodeEnd(FacesContext context) throws IOException {
super.encodeEnd(context);
if (getVar() != null) {
ResponseWriter originalResponseWriter = (ResponseWriter) context.getAttributes().remove(this + "_writer");
StringWriter buffer = (StringWriter) context.getAttributes().remove(this + "_buffer");
context.setResponseWriter(originalResponseWriter);
context.getExternalContext().getRequestMap().put(getVar(), buffer.toString());
}
}
// Attribute getters/setters --------------------------------------------------------------------------------------
/**
* Returns the variable name which exposes the captured output into the request scope.
* @return The variable name which exposes the captured output into the request scope.
*/
public String getVar() {
return state.get(PropertyKeys.var);
}
/**
* Sets the variable name which exposes the captured output into the request scope.
* @param var The variable name which exposes the captured output into the request scope.
*/
public void setVar(String var) {
state.put(PropertyKeys.var, var);
}
}