/*
* 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.faces.FacesException;
import javax.faces.component.FacesComponent;
import javax.faces.component.UIParameter;
import javax.faces.component.ValueHolder;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.convert.Converter;
import org.omnifaces.component.ParamHolder;
/**
* <p>
* The <code><o:param></code> is a component that extends the standard {@link UIParameter} to implement {@link ValueHolder}
* and thus support a {@link Converter} to convert the supplied value to string, if necessary.
* <p>
* You can use it the same way as <code><f:param></code>, you only need to change <code>f:</code> into
* <code>o:</code> to get the extra support for a {@link Converter} by usual means via the <code>converter</code>
* attribute of the tag, or the nested <code><f:converter></code> tag, or just automatically if a converter is
* already registered for the target class via <code>@FacesConverter(forClass)</code>.
* <p>
* Also, if no value is specified, but children are present, then the encoded output of children will be returned as
* param value. This is useful when you want to supply JSF components or HTML as parameter of an unescaped
* <code><h:outputFormat></code>. For example,
* <pre>
* <h:outputFormat value="#{bundle.paragraph}" escape="false">
* <o:param><h:link outcome="contact" value="#{bundle.contact}" /></o:param>
* </h:outputFormat>
* </pre>
* <p>with this bundle
* <pre>
* paragraph = Please {0} for more information.
* contact = contact us
* </pre>
* <p>will result in the link being actually encoded as output format parameter value.
*
* @author Bauke Scholtz
* @since 1.4
* @see ParamHolder
*/
@FacesComponent(Param.COMPONENT_TYPE)
public class Param extends UIParameter implements ParamHolder {
// Public constants -----------------------------------------------------------------------------------------------
public static final String COMPONENT_TYPE = "org.omnifaces.component.output.Param";
// Private constants ----------------------------------------------------------------------------------------------
private enum PropertyKeys {
// Cannot be uppercased. They have to exactly match the attribute names.
converter;
}
// Properties -----------------------------------------------------------------------------------------------------
private Converter localConverter;
// Attribute getters/setters --------------------------------------------------------------------------------------
@Override
public Converter getConverter() {
return localConverter != null ? localConverter : (Converter) getStateHelper().eval(PropertyKeys.converter);
}
@Override
public void setConverter(Converter converter) {
localConverter = converter;
}
@Override
public Object getLocalValue() {
return super.getValue();
}
@Override
public Object getValue() {
FacesContext context = getFacesContext();
Converter converter = getConverter();
Object value = getLocalValue();
if (value == null && getChildCount() > 0) {
ResponseWriter originalResponseWriter = context.getResponseWriter();
StringWriter output = new StringWriter();
context.setResponseWriter(originalResponseWriter.cloneWithWriter(output));
try {
super.encodeChildren(context);
}
catch (IOException e) {
throw new FacesException(e);
}
finally {
context.setResponseWriter(originalResponseWriter);
}
value = output.toString();
}
if (converter == null && value != null) {
converter = context.getApplication().createConverter(value.getClass());
}
if (converter != null) {
return converter.getAsString(context, this, value);
}
else {
return value;
}
}
@Override
public boolean getRendersChildren() {
return true;
}
@Override
public void encodeChildren(FacesContext context) throws IOException {
// This override which does nothing effectively blocks the children from being encoded during JSF render.
}
}