Custom converter with attributes/injection not working with anymore after upgrade to JSF 2.3 - cdi

I am trying to upgrade/use a custom converter that worked with JSF 2.2 (on Wildfly 13) to work on JSF 2.3 (with Mojarra 2.3.9.SP02 running on Wildfly 17.0.1)
The converter is used via its own tag defined in a tag library.
Everything's fine as long as no tag attributes are used. The attributes are just not set in the converter. The setters never get called.
But if I remove the managed = true from the converter the attributes are set but then the injection no longer works.
The converter is used like this:
<h:inputText id="text" value="#{welcome.text}">
<cdijsf:converterWithAttr id="myConverter" attr="myAttrValue" />
</h:inputText>
Tag library:
<facelet-taglib version="2.3"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facelettaglibrary_2_3.xsd">
<namespace>http://cdijsf.transdata.net/jsf</namespace>
<tag>
<tag-name>converterWithAttr</tag-name>
<converter>
<converter-id>cdijsf.ConverterWithAttr</converter-id>
</converter>
<attribute>
<name>attr</name>
<type>java.lang.String</type>
</attribute>
</tag>
</facelet-taglib>
And this is the converter code:
#Dependent
#FacesConverter(value = "cdijsf.ConverterWithAttr", managed = true)
public class ConverterWithAttr implements Converter<String> {
#Inject
private BeanManager beanManager;
private String attr;
public ConverterWithAttr() {
}
#PostConstruct
private void init() {
// If 'managed = true' beanManager is injected at this point.
// If 'managed = false' beanManager is null at this point
}
#Override
public String getAsObject(FacesContext context, UIComponent component, String value) {
return value;
}
#Override
public String getAsString(FacesContext context, UIComponent component, String value) {
return value;
}
public String getAttr() {
return attr;
}
public void setAttr(String attr) {
// If 'managed = true' setAttr is never called
// If 'managed = false' setAttr is called
this.attr = attr;
}
}
faces-config.xml:
<faces-config version="2.3"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_3.xsd">
</faces-config>
And I also explicitly declare JSF 2.3 like this:
#FacesConfig(version = Version.JSF_2_3)
#ApplicationScoped
public class JsfConfiguration {
}
beans.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd"
version="2.0"
bean-discovery-mode="annotated">
</beans>

Related

Adding an attribute to primefaces by extending commandButton component

Primefaces 8
Java 11
I would like to add a controller attribute to a commandButton which takes in page bean instance as value . I can then use this page bean in command button renderer to disable the button by calling method on page bean.
Component Bean class
#FacesComponent(CustomCommandButton.COMPONENT_TYPE)
public class CustomCommandButton extends CommandButton
{
private static final Logger logger = Logger.getInstance(CustomCommandButton.class);
public static final String COMPONENT_FAMILY = "com.xxx.faces.component";
public static final String COMPONENT_TYPE = "com.xxx.faces.component.CustomCommandButton";
private enum PropertyKeys {
controller
}
private IpmsBean controller;
#Override
public String getFamily() {
return COMPONENT_FAMILY;
}
#Override
public String getRendererType() {
return CustomCommandButtonRenderer.RENDERER_TYPE;
}
public CustomCommandButton()
{
super();
}
public void setController(IpmsBean bean)
{
getStateHelper().put(PropertyKeys.controller, bean);
this.controller=bean;
}
public IpmsBean getController()
{
return (IpmsBean) getStateHelper().eval(PropertyKeys.controller, null);
}
}
Renderer class which doesn't have whole lot just yet:
#FacesRenderer(
componentFamily=CustomCommandButton.COMPONENT_FAMILY,
rendererType=CustomCommandButtonRenderer.RENDERER_TYPE
)
public class CustomCommandButtonRenderer extends CommandButtonRenderer {
public static final String RENDERER_TYPE = "com.xxx.faces.component.CustomCommandButtonRenderer";
#Override
public void encodeEnd(FacesContext context, UIComponent component)
throws java.io.IOException {
//plan is to disable button after asking pagebean
//if (component.getController().shouldDisable())
// component.setDisabled("true");
//
super.encodeEnd(context, component);
}
}
taglibs-faces.xml
<?xml version="1.0"?>
<facelet-taglib version="2.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd"
id="lu">
<namespace>http://com.xxx.faces/ui</namespace>
<tag>
<tag-name>customCommandButton</tag-name>
<component>
<component-type>com.xxx.faces.component.CustomCommandButton</component-type>
<renderer-type>com.xxx.faces.component.CustomCommandButtonRenderer</renderer-type>
</component>
<attribute>
<description>
<![CDATA[Any instance of IpmsBean.]]>
</description>
<name>controller</name>
<required>false</required>
<type>com.xxx.xxxx.client.jsf.bean.IpmsBean</type>
</attribute>
...copied rest of commandbutton's attributes here...
</tag>
</facelet-taglib>
xhtml page:
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:p="http://primefaces.org/ui"
template="/resources/layout/masterLayout.xhtml"
xmlns:c="http://xmlns.jcp.org/jsf/core"
xmlns:incl="http://java.sun.com/jsf/composite/includes"
xmlns:lu="http://com.xxx.faces/ui">
<lu:customCommandButton
value="Custom Button"
controller="#{regionPolicyBean}"
oncomplete="hello()"/>
....
</ui:composition>
"Custom Button" shows up just fine. But setController(bean) method is not valled. setOnComplete() method is called. Not sure what am I missing here.
I did refer to this answer Adding Custom Attributes to Primefaces Autocomplete Component in JSF but still not sure why setter is not being called.
Not a direct answer, but I don't see why you do this the hard way. You can simply do:
<p:commandButton ...>
<f:attribute name="controller" value="#{...}"/>
</p:commandButton>
and get controller from the component's attributes map.

cdi bean injection for initialization code

I have a bean configured which have some initialization logic. I have annotated this bean using #ApplicationScoped annotation. But somehow, cdi is not picking this bean.
beans.xml content:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
bean-discovery-mode="annotated">
</beans>
Bean file:
#ApplicationScoped
public class Initializer{
#Inject #ConfigProperty(name = "app.name")
private String appName;
#Inject #ConfigProperty(name = "app.token")
private String appToken;
#Inject #ConfigProperty(name = "app.version")
private String appVersion;
#PostConstruct
public void init() {
System.out.println("flow should come here....); //but this line does not execute.
}
}
Code to read config file:
#Exclude
public class ConfigurationFile implements PropertyFileConfig {
#Override
public String getPropertyFileName() {
String env = Util.getEnv();
switch (env) {
case "dev":
case "uat":
case "prod":
return "config/" + env + "/default.properties";
default:
return "config/default.properties";
}
}
#Override
public boolean isOptional() {
return false;
}
}
I am using:
cdiL: for dependency injection,
apache-deltaspike: for reading config file.
wildfly-swarm: server
I have got the solution to this problem.
Issue is solved by changing the method declaration as follows:
public void init(#Observes #Initialized(ApplicationScoped.class) Object init) {
//................code logic here................
}

#Specializes in Wildfly 10.1.0

We are trying to use alternative bean instance injection for our integration test suite deployed on a Wildfly 10.1.0 server.
According to the CDI 1.2 spec, a possible solution to do so would be to use the #Specializes annotation on an alternative deployed in the integration test archive only.
However, the default implementation is always injected. We tried #Specializes on managed beans, session beans, and tried to select the alternatives in the beans.xml file.
The following example illustrate the issue:
BeanInterface.java
public interface BeanInterface {
void work();
}
Implementation1.java
#Dependent
public class Implementation1 implements BeanInterface {
#Override
public void work() {
System.out.println("test 1");
}
}
Implementation2
#Dependent
#Alternative
#Specializes
public class Implementation2 extends Implementation1 {
#Override
public void work() {
System.out.println("test 2");
}
}
TestSingleton.java:
#Singleton
#Startup
public class TestSingleton {
#Inject
private BeanInterface beanInterface;
#PostConstruct
public void init() {
this.beanInterface.work();
}
}
Packaging these classes in a war (with a web.xml) and deploying on wildfly, the implementation 1 is always injected in the Stateless bean.
Wildfly 10.1.0 uses weld-2.3.SP2 which implements CDI 1.2.
Thanks,
Charly
Although it does not make the #Specializes annotation work as expected, this solution suggested by John Ament allows to inject the second Implementation.
Just change the #javax.enterprise.inject.Specializes annotation with #javax.annotation.Priority (and some value):
#Dependent
#Alternative
#Priority(100)
public class Implementation2 extends Implementation1 {
#Override
public void work() {
System.out.println("test 2");
}
}
Also missing in the OP question was the beans.xml (not web.xml) packaged in the WEB-INF:
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
bean-discovery-mode="all">
</beans>

Create custom renderer for UIInput

I want to create custom renderer for UIInput component. Et the SourcePackage I created Renderers package and class Renderers.TextFieldRenderer, which is following
#FacesRenderer(componentFamily = "javax.faces.UIInput", rendererType = "text")
public class TextFieldRender extends Renderer {
#Override
public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
ResponseWriter writer = context.getResponseWriter();
String clientId = component.getClientId(context);
String hint= (String) component.getAttributes().get("placeholder");
writer.startElement("input", component);
writer.writeAttribute("name", clientId, null);
writer.writeAttribute("placeholder", hint, "hint");
writer.endElement("input");
}
}
Further i'm created faces-config.xml
<faces-config
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
version="2.0">
<component>
<component-type>mp.text</component-type>
<component-class>javax.faces.component.UIInput</component-class>
</component>
<render-kit>
<renderer>
<component-family>javax.faces.component.UIInput</component-family>
<renderer-type>mp.textrender</renderer-type>
<renderer-class>Renderers.TextFieldRenderer</renderer-class>
</renderer>
</render-kit>
</faces-config>
and text.taglib.xml
<?xml version="1.0" encoding="UTF-8"?>
<facelet-taglib>
<namespace>http://text.com</namespace>
<tag>
<tag-name>hintText</tag-name>
<component>
<component-type>mp.text</component-type>
<renderer-type>mp.textrenderer</renderer-type>
</component>
</facelet-taglib>
But it doesnt work. Even I'm cant use namespace whuch defined in text.taglib.xml. When I'm wrong?

Custom converter in JSF 2 with arguments

I'm trying to implement a custom truncate converter, which truncates a string at a given index and adds a continuation symbol. The converter works fine, only when i hard code the parameters, as they are not being passed to the backend. What am I doing wrong?
The parameters are properties of the converter class:
#FacesConverter(value = TruncateConverter.CONVERTER_ID)
public class TruncateConverter implements Converter, StateHolder
{
public static final String CONVERTER_ID = "bla.blablabla.Truncate";
private int truncateIndex;
private String contSymbol;
Here is how i'm using the converter (or trying to):
<h:outputText id="news-text-left" value="#{newsListBean.newsList_teaser.text}">
<f:converter converterId="bla.blablabla.Truncate" truncateIndex="150" contSymbol="..." />
</h:outputText>
I googled around for quite a bit and wasn't able to find a single example of a JSF2 converter with parameters... Thank you guys for your help, really appreciate it!
You may take a look at JSF2.0 sources. For example DateTimeConverter... JSF sources available here in svn reposotory: https://svn.java.net/svn/mojarra~svn/trunk
IMO creating such converter is not easy. It also required to create converter tag to register converter.
Other way to pass some data to converter is attibutes. So You can write
<h:outputText ...>
<f:converter converterId="bla.blablabla.Truncate" />
<f:attribute name="truncateIndex" value="150"/>
</h:outputText>
Than call to component.getAttributes().get("truncateIndex");
in converter code.
Based on #Maks solution: It's possible to combine the converter and the attribute in one tag:
<facelet-taglib version="2.2"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facelettaglibrary_2_2.xsd">
<namespace>http://mycompany.com/some-identifier</namespace>
<tag>
<tag-name>truncate</tag-name>
<converter>
<converter-id>bla.blablabla.Truncate</converter-id>
</converter>
<attribute>
<name>truncateIndex</name>
</attribute>
</tag>
</facelet-taglib>
Then you can use the converter like this:
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:my="http://mycompany.com/some-identifier">
<my:truncate truncateIndex="150" />
</ui:composition>
You also don't need to grab the parameter from the component-attributes. A bean-property with the same name will be populated automatically:
#FacesConverter("bla.blablabla.Truncate")
public class Truncate implements Converter {
private String truncateIndex;
// getters + setters
...
}
http://jerryorr.blogspot.nl/2011/10/creating-jsf-12-custom-converter-with.html is a good guide to set up your first custom converter with parameters
This is how I did it (in a separated jar file, and using maven's default directories):
1) Create the converter's class (inside src/main/java)
2) Create a .taglib.xml class (inside src/main/resources/META-INF)
3) Create a faces-config.xml (inside src/main/resources/META-INF)
Example:
Step 1)
package com.ocabit.jsf.converter;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.NumberFormat;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.convert.FacesConverter;
import org.springframework.stereotype.Service;
import com.ocabit.utils.currency.CurrencyUtils;
/**
* Converter para valores BigDecimal.
*
* #author Carlos Eduardo Pauluk
*
*/
#FacesConverter("bigDecimalConverter")
#Service("bigDecimalConverter")
public class BigDecimalConverter implements Converter, Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
public static final String CONVERTER_ID = "com.ocabit.jsf.converter.BigDecimalConverter";
/**
* Em caso de null, força a saída para 0.0;
*/
private boolean nullToZero = false;
/**
* Em caso de zero, força a saída para null;.
*/
private boolean zeroToNull = false;
/**
* Só retorna números positivos.
*/
private boolean onlyAbs = false;
/**
* Só retorna números negativos.
*/
private boolean onlyNeg = false;
/**
* Quantidade de casas decimais.
*/
private int decimals = 2;
#Override
public Object getAsObject(final FacesContext context, final UIComponent component, final String value) {
try {
BigDecimal bd = new BigDecimal(value);
bd = bd.setScale(getDecimals(), RoundingMode.HALF_DOWN);
if (bd.equals(BigDecimal.ZERO) && isZeroToNull()) {
return null;
}
if (isOnlyAbs()) {
bd = bd.abs();
}
if (isOnlyNeg()) {
bd = bd.abs();
bd = bd.negate();
}
return bd;
} catch (final Exception e) {
BigDecimal bd = null;
if (isNullToZero()) {
bd = CurrencyUtils.getBigDecimalCurrency("0.0");
bd = bd.setScale(getDecimals(), RoundingMode.HALF_DOWN);
}
return bd;
}
}
#Override
public String getAsString(final FacesContext context, final UIComponent component, final Object value) {
if (!(value instanceof BigDecimal)) {
throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erro ao converter o valor decimal.", ""));
}
final NumberFormat nf = NumberFormat.getInstance();
// sempre terá 2 casas decimais
nf.setMinimumFractionDigits(2);
nf.setMaximumFractionDigits(2);
return nf.format(((BigDecimal) value).doubleValue());
}
public boolean isNullToZero() {
return nullToZero;
}
public void setNullToZero(boolean nullToZero) {
this.nullToZero = nullToZero;
}
public boolean isZeroToNull() {
return zeroToNull;
}
public void setZeroToNull(boolean zeroToNull) {
this.zeroToNull = zeroToNull;
}
public boolean isOnlyAbs() {
return onlyAbs;
}
public void setOnlyAbs(boolean onlyAbs) {
this.onlyAbs = onlyAbs;
}
public boolean isOnlyNeg() {
return onlyNeg;
}
public void setOnlyNeg(boolean onlyNeg) {
this.onlyNeg = onlyNeg;
}
public int getDecimals() {
return decimals;
}
public void setDecimals(int decimals) {
this.decimals = decimals;
}
}
Step 2)
<!DOCTYPE facelet-taglib PUBLIC
"-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
"facelet-taglib_1_0.dtd">
<facelet-taglib version="2.2"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facelettaglibrary_2_2.xsd">
<namespace>http://ocabit.com.br/facelets</namespace>
<tag>
<tag-name>convertBigDecimal</tag-name>
<converter>
<converter-id>com.ocabit.jsf.converter.BigDecimalConverter</converter-id>
</converter>
</tag>
</facelet-taglib>
Step 3)
<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd"
version="2.2">
<converter>
<description></description>
<converter-id>com.ocabit.jsf.converter.BigDecimalConverter</converter-id>
<converter-class>com.ocabit.jsf.converter.BigDecimalConverter</converter-class>
<property>
<property-name>nullToZero</property-name>
<property-class>boolean</property-class>
<description>Ao invés de retornar 'null', retorna '0.0'</description>
</property>
<property>
<property-name>zeroToNull</property-name>
<property-class>boolean</property-class>
<description>Ao invés de retornar '0.0', retorna 'null'</description>
</property>
<property>
<property-name>onlyAbs</property-name>
<property-class>boolean</property-class>
<description>Somente retorna números positivos</description>
</property>
<property>
<property-name>onlyNeg</property-name>
<property-class>boolean</property-class>
<description>Somente retorna números negativos</description>
</property>
<property>
<property-name>decimals</property-name>
<property-class>int</property-class>
<description>Quantidade de casas decimais</description>
</property>
</converter>
</faces-config>
Voilà.

Resources