/*
* 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.taghandler;
import static org.omnifaces.taghandler.DeferredTagHandlerHelper.collectDeferredAttributes;
import static org.omnifaces.taghandler.DeferredTagHandlerHelper.createInstance;
import java.io.IOException;
import java.io.Serializable;
import javax.faces.application.Application;
import javax.faces.component.UIComponent;
import javax.faces.component.ValueHolder;
import javax.faces.context.FacesContext;
import javax.faces.view.facelets.ComponentHandler;
import javax.faces.view.facelets.ConverterConfig;
import javax.faces.view.facelets.ConverterHandler;
import javax.faces.view.facelets.FaceletContext;
import javax.faces.view.facelets.TagAttribute;
import javax.faces.view.facelets.TagHandlerDelegate;
import org.omnifaces.taghandler.DeferredTagHandlerHelper.DeferredAttributes;
import org.omnifaces.taghandler.DeferredTagHandlerHelper.DeferredTagHandler;
import org.omnifaces.taghandler.DeferredTagHandlerHelper.DeferredTagHandlerDelegate;
/**
* <p>
* The <code><o:converter></code> is a taghandler that extends the standard <code><f:converter></code> tag
* family with support for deferred value expressions in all attributes. In other words, the converter attributes are
* not evaluated anymore on a per view build time basis, but just on every access like as with UI components and bean
* properties. This has among others the advantage that they can be evaluated on a per-iteration basis inside an
* iterating component, and that they can be set on a custom converter without needing to explicitly register it in a
* tagfile.
*
* <h3>Usage</h3>
* <p>
* When you specify for example the standard <code><f:convertDateTime></code> by
* <code>converterId="javax.faces.DateTime"</code>, then you'll be able to use all its attributes such as
* <code>pattern</code> and <code>locale</code> as per its documentation, but then with the possibility to supply
* deferred value expressions.
* <pre>
* <o:converter converterId="javax.faces.DateTime" pattern="#{item.pattern}" locale="#{item.locale}" />
* </pre>
* <p>
* The converter ID of all standard JSF converters can be found in
* <a href="http://docs.oracle.com/javaee/7/api/javax/faces/convert/package-summary.html">their javadocs</a>.
* First go to the javadoc of the class of interest, then go to <code>CONVERTER_ID</code> in its field summary
* and finally click the Constant Field Values link to see the value.
*
* @author Bauke Scholtz
* @see DeferredTagHandlerHelper
*/
public class Converter extends ConverterHandler implements DeferredTagHandler {
// Constructors ---------------------------------------------------------------------------------------------------
/**
* The constructor.
* @param config The converter config.
*/
public Converter(ConverterConfig config) {
super(config);
}
// Actions --------------------------------------------------------------------------------------------------------
/**
* Create a {@link javax.faces.convert.Converter} based on the <code>binding</code> and/or <code>converterId</code>
* attributes as per the standard JSF <code><f:converter></code> implementation and collect the render time
* attributes. Then create an anonymous <code>Converter</code> implementation which wraps the created
* <code>Converter</code> and delegates the methods to it after setting the render time attributes. Finally set the
* anonymous implementation on the parent component.
* @param context The involved facelet context.
* @param parent The parent component to set the <code>Converter</code> on.
* @throws IOException If something fails at I/O level.
*/
@Override
public void apply(FaceletContext context, UIComponent parent) throws IOException {
if (!ComponentHandler.isNew(parent) && UIComponent.getCompositeComponentParent(parent) == null) {
// If it's not new nor inside a composite component, we're finished.
return;
}
if (!(parent instanceof ValueHolder)) {
// It's likely a composite component. TagHandlerDelegate will pickup it and pass the target component back.
super.apply(context, parent);
return;
}
final javax.faces.convert.Converter converter = createInstance(context, this, "converterId");
final DeferredAttributes attributes = collectDeferredAttributes(context, this, converter);
((ValueHolder) parent).setConverter(new DeferredConverter() {
private static final long serialVersionUID = 1L;
@Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
attributes.invokeSetters(context.getELContext(), converter);
return converter.getAsObject(context, component, value);
}
@Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
attributes.invokeSetters(context.getELContext(), converter);
return converter.getAsString(context, component, value);
}
});
}
@Override
@SuppressWarnings("unchecked")
public <T> T create(Application application, String id) {
return (T) application.createConverter(id);
}
@Override
public TagAttribute getTagAttribute(String name) {
return getAttribute(name);
}
@Override
protected TagHandlerDelegate getTagHandlerDelegate() {
return new DeferredTagHandlerDelegate(this, super.getTagHandlerDelegate());
}
@Override
public boolean isDisabled(FaceletContext context) {
return false; // This attribute isn't supported on converters anyway.
}
// Nested classes -------------------------------------------------------------------------------------------------
/**
* So that we can have a serializable converter.
*
* @author Bauke Scholtz
*/
protected abstract static class DeferredConverter implements javax.faces.convert.Converter, Serializable {
private static final long serialVersionUID = 1L;
}
}