3
votes

I am new to JSF and still learning. I tried searching for a solution to my specific problem described below but I could not find anything. If it because I was searching for the wrong things, please point me in the right direction, but hopefully it is something that hasn't been answered and an answer can benefit everyone.

The following example illustrates the problem I came across. The example is simplified to focus on the problem and to hide the complexities of the actual project in which the problem occurred.

Consider the following pages / classes:

  1. /resources/test/custom.xhtml;
  2. /test/CharsetProvider.java;
  3. /test/CharsetHello.java;
  4. /testWith.xhtml;
  5. /testWithout.xhtml;

/resources/test/custom.xhtml

This is composite component with one attribute with a default value. The component simply takes the attribute value and passes it as an argument to the CDI bean described below in order obtain the model object used for output.

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:cc="http://java.sun.com/jsf/composite"
      xmlns:h="http://java.sun.com/jsf/html">

    <cc:interface>
        <cc:attribute name="charset"
                      default="#{charsetProvider.defaultCharset}"
                      type="java.nio.charset.Charset" />
    </cc:interface>

    <cc:implementation>
        <h:outputText value="#{charsetProvider.createCharsetHello(cc.attrs.charset).hello}"/>
    </cc:implementation>
</html>

test/CharsetProvider.java

This is a CDI bean that simply contains a default value used throughout the application and has a method that creates an object used as the model for a component. The reason I use a CDI bean instead of a backing bean is because in my specific project the default value needs to be injected at runtime, but backing beans are not candidates for injection.

package test;

import java.nio.charset.Charset;
import javax.annotation.PostConstruct;
import javax.faces.bean.SessionScoped;
import javax.inject.Named;

@Named
@SessionScoped
public class CharsetProvider {

    private Charset defaultCharset;

    @PostConstruct
    protected void postConstruct() {
        this.defaultCharset = Charset.forName("UTF-8");
    }

    public Charset getDefaultCharset() {
        return defaultCharset;
    }

    public Charset getCharsetForName(String name) {
        return Charset.forName(name);
    }

    public CharsetHello createCharsetHello(Charset cs) {
        return new CharsetHello(cs);
    }
}

test/CharsetHello.java

This is the "model" object. It simply converts "Hello world!" to a byte array and back using the given charset.

package test;

import java.nio.charset.Charset;

public class CharsetHello {

    private static final String HW = "Hello World!";
    private final byte[] data;
    private final Charset cs;

    public CharsetHello(Charset cs) {
        this.cs = cs;
        this.data = CharsetHello.HW.getBytes(this.cs);
    }

    public String getHello() {
        return new String(this.data, this.cs);
    }
}

testWith.xhtml

This is a test page that uses the composite component defined above by specifying a value for the component's attribute. The page renders properly, i.e. "Hello World!" prints on the screen.

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:test="http://java.sun.com/jsf/composite/test">
    <h:head>
        <title>Test</title>
    </h:head>
    <h:body>
        <test:custom charset="#{charsetProvider.getCharsetForName('UTF-16')}" />
    </h:body>
</html>

testWithout.xhtml

This is a test page that does not pass a custom value to the component's attribute, intending to use the default value.

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:test="http://java.sun.com/jsf/composite/test">
    <h:head>
        <title>Test</title>
    </h:head>
    <h:body>
        <test:custom />
    </h:body>
</html>

The page above results in a JSF error page with the following message:

/resources/test/custom.xhtml @14,94 value="#{charsetProvider.createCharsetHello(cc.attrs.charset).hello}": Cannot convert UTF-8 of type class java.lang.String to class java.nio.charset.Charset

It seems that in the last case the default value is converted to a java.lang.String before being passed to the method.

First off, is this the expected behaviour and why?

If this is the expected behaviour, can you suggest a different implementation?

Thank you in advance!

1
I was able to reproduce it. Not sure if this is specified behaviour, I'd have to look in the spec first. I at least didn't intuitively expect it. Though, it works fine for types for which EL has builtin coercion, such as Number, Boolean and Enum (as long as you don't use Integer which would get interpreted as Long).BalusC

1 Answers

2
votes

This problem has exactly the same ground as this problem: FacesConverter forClass don't work with Composite Componet. The composite attribute value type is in Mojarra incorrectly been evaluated as java.lang.Object instead of the actual model type.

It's been reported as Mojarra issue 2568. It works in MyFaces 2.1.9.