/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.myfaces.orchestra.conversation.jsf.components;
import javax.faces.application.Application;
import javax.faces.component.UIComponent;
import javax.faces.component.ValueHolder;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.webapp.UIComponentTag;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.Tag;
import javax.servlet.jsp.tagext.TagSupport;
import org.apache.myfaces.orchestra.lib.jsf.SerializableConverter;
/**
* Works like f:converter except that the converter instance is a managed-bean
* instance rather than a simple class.
* <p>
* In addition, the retrieved Converter instance is (by default) wrapped in
* a SerializableConverter instance. See the documentation for that class
* for further details.
* <p>
* This is not actually orchestra-specific functionality; this is something
* that the JSF core library could offer, or an add-on library such as Tomahawk.
* But at the current time, no common library offers this feature and the
* use of a SerializableConverter wrapper is very convenient.
* <p>
* The primary use-case for this tag is custom Converter classes that access
* conversation state. For example, a Converter may be written to convert
* an object instance into a string by reading its primary key, and on
* postback convert the string (a primary key) back into an object
* instance by loading it using the persistence context associated with
* a particular conversation. Of course such a converter must be configured
* with the appropriate Orchestra scope, and therefore must be loaded as a
* managed bean rather than via the standard f:converter mechanism.
* <p>
* An alternative to using this tag is simply to use the standard
* "converter" attribute available on most JSF component tags, but if
* a SerializableConverter wrapper is desired then two bean definitions are
* needed rather than just one; see class SerializableConverter for details.
* <p>
* <h2>Creating custom converter tags</h2>
*
* If you have written a custom Converter instance that can be configured
* then configuration may be done via the managed-bean system. However it
* can also be nice to configure instances via the tag, like the standard
* f:dateTimeConverter or f:numberConverter. To do this, you can:
* <ul>
* <li>subclass this tag
* <li>add setters for all the properties that you want configurable via the tag
* <li>override the createConverter method and copy the tag properties onto the
* newly created converter instance.
* <li>implement the StateHolder interface on the Converter class in order to
* save and restore these custom properties.
* </ul>
*/
public class ConverterTag extends TagSupport
{
private static final long serialVersionUID = 1L;
private String beanName;
private boolean useWrapper = true;
public ConverterTag()
{
super();
}
public void setBeanName(String beanName)
{
this.beanName = beanName;
}
public void setUseWrapper(boolean enabled)
{
this.useWrapper = enabled;
}
public int doStartTag()
throws JspException
{
UIComponentTag componentTag = UIComponentTag.getParentUIComponentTag(pageContext);
if (componentTag == null)
{
throw new JspException("no parent UIComponentTag found");
}
if (!componentTag.getCreated())
{
return Tag.SKIP_BODY;
}
Converter converter = createConverter(beanName);
if (useWrapper && (converter instanceof SerializableConverter == false))
{
// Needed to check if it is already of the specified type in case the
// managed-bean framework has been configured to auto-wrap Converter
// instances already (eg via a Spring BeanPostProcessor or equivalent).
// This isn't the case, so wrap it now.
converter = new SerializableConverter(beanName, converter);
}
UIComponent component = componentTag.getComponentInstance();
if (component == null)
{
throw new JspException("parent UIComponentTag has no UIComponent");
}
if (!(component instanceof ValueHolder))
{
throw new JspException("UIComponent is no ValueHolder");
}
((ValueHolder)component).setConverter(converter);
return Tag.SKIP_BODY;
}
public void release()
{
super.release();
beanName = null;
}
/**
* Override this method in order to customise the bean instance.
*/
protected static Converter createConverter(String beanName)
throws JspException
{
FacesContext facesContext = FacesContext.getCurrentInstance();
Application application = facesContext.getApplication();
Object converter = application.getVariableResolver().resolveVariable(facesContext, beanName);
return (Converter) converter;
}
}