@Inject to pass params to a CDI @Named bean via URL
Asked Answered
T

3

8

If I cannot use the @ManagedProperty annotation with @Named, because @ManagedProperty doesn't work in CDI(?), then how do you pass params in the URL to the facelets client? In my code, I want to pass javax.mail.getMessageNumber() to details.xhtml through the "back" and "forward" buttons.

I understand that @Inject should be used, but what is being injected and how, please?

From the glassfish logs, id is always 0, which is quite odd. Even when "forward" is clicked, id never gets above 1 no matter how many times the button is clicked. Of course, that's merely a symptom of the problem. The desired output, of course, is to advance to the next Message.

Perhaps put the Message, or at least the int, into the session?

The client as so:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns:ui="http://java.sun.com/jsf/facelets"
                template="./template.xhtml"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:c="http://java.sun.com/jsp/jstl/core"
                xmlns:f="http://java.sun.com/jsf/core">
    <ui:define name="top">
        <h:form>
            <h:form>
                <h:outputLink id="link1" value="detail.xhtml">
                    <f:param name="id" value="#{detail.back()}" />
                    <h:outputText value="back" />
                </h:outputLink>
            </h:form>
        </h:form>
        <h:form>
            <h:outputLink id="link1" value="detail.xhtml">
                <f:param name="id" value="#{detail.forward()}" />
                <h:outputText value="forward" />
            </h:outputLink>
        </h:form>
    </ui:define>
    <ui:define name="content">
        <h:outputText value="#{detail.content}"></h:outputText>
    </ui:define>
</ui:composition>

and the bean as so:

package net.bounceme.dur.nntp;

import java.util.logging.Level;
import java.util.logging.Logger;
import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedProperty;
import javax.inject.Named;
import javax.mail.Message;

@Named
@RequestScoped
public class Detail {

    private static final Logger logger = Logger.getLogger(Detail.class.getName());
    private static final Level level = Level.INFO;
    @ManagedProperty(value = "#{param.id}")
    private Integer id = 0;
    private Message message = null;
    private SingletonNNTP nntp = SingletonNNTP.INSTANCE;

    public Detail() {
        message = nntp.getMessage(id);
    }

    public int forward() {
        logger.log(level, "Detail.forward.." + id);
        id = id + 1;
        logger.log(level, "..Detail.forward " + id);
        return id;
    }

    public int back() {
        logger.log(level, "Detail.back.." + id);
        id = id - 1;
        logger.log(level, "..Detail.back " + id);
        return id;
    }

    public Message getMessage() {
        return message;
    }

    public String getContent() throws Exception {
        return message.getContent().toString();
    }
}
Transpadane answered 7/4, 2012 at 22:19 Comment(1)
I asked to have this question deleted because it's a bit of a flawed question.Transpadane
H
14

This works only with the in JSF 2.3 introduced javax.faces.annotation.ManagedProperty.

@Inject @ManagedProperty("#{param.id}")
private String id;

The now deprecated javax.faces.bean.ManagedProperty annotation works only in JSF @ManagedBean classes. I.e. in instances which are managed by JSF. It does not work in instances which are managed by CDI @Named. Further, you've made another mistake: you're trying to prepare the Message based on the managed property in the constructor. If it were a real @ManagedBean, that would also not have worked. The managed property is not available during construction, simply because it's not possible to call the setter method before the constructor is called. You should have used a @PostConstruct method for this.

If you cannot upgrade to JSF 2.3, you'd need to create a custom CDI annotation. A concrete example is posted in this blog. Here's an extract of relevance:

The custom @HttpParam annotation:

@Qualifier
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface HttpParam {
    @NonBinding
    public String value() default "";
}

The annotation value producer:

public class HttpParamProducer {

    @Inject
    FacesContext facesContext;

    @Produces
    @HttpParam
    String getHttpParameter(InjectionPoint ip) {
        String name = ip.getAnnotated().getAnnotation(HttpParam.class).value();
        if ("".equals(name)) name = ip.getMember().getName();
        return facesContext.getExternalContext()
                .getRequestParameterMap()
                .get(name);
    }
}

An usage example:

@Inject @HttpParam
private String id;

JSF utility library OmniFaces has a @Param for exactly this purpose, with builtin support for JSF conversion and validation.


Alternatively, you can also manually grab the request parameter from the external context in the Detail managed bean. The recommended way to do managed bean initialization is to use a @PostConstruct method, not the constructor, as the constructor could be used for completely different purposes than managed bean creation:

@PostConstruct
public void init() {
    String id = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("id");
    // ...
}

Another way, IMO also more suitable for this particular case, is to use <f:viewParam> which also allows you to convert the ID to Message directly by a custom converter.

<f:metadata>
    <f:viewParam name="id" value="#{detail.message}" converter="messageConverter" />
</f:metadata>

with just

@Named
public class Detail {

    private Message message;

    // Getter+setter
}

and a

@FacesConverter("messageConverter")
public class MessageConverter implements Converter {

    // Convert string id to Message object in getAsObject().
    // Convert Message object to string id in getAsString().

}

See also

Hothouse answered 7/4, 2012 at 23:35 Comment(4)
why do you need a custom FacesConverter?Transpadane
So that you can convert a String ID to object Message in a reuseable manner.Hothouse
I think I'll hold off on implementing that for now, but that makes much more sense then parsing strings. see clarifying question.Transpadane
It is 2020 and has anything changed with Jakarta?Gittern
R
1

First, to explain the alien part - Glassfish uses JBoss Weld as its CDI implementation, Oracle does not develop an implementation of its own.

And concerning the meaning of the error message: FacesContext is simply not injectable via @Inject. There is an rather old feature request for that, and I think Seam or Solder provide a producer. But there's no need to integrate either of the libraries just for that. Access faces context like you would in normal managed bean, via FacesContext.getCurrentInstance().

Rudimentary answered 8/4, 2012 at 19:58 Comment(1)
So, where HttpParamProducer uses @Inject the FacesContext is not injectable? To back this up a bit, why, in this scenario, would you want the FacesContext, for the parameters in the current instance? I know I'm flailing about a bit, pardon.Transpadane
T
0

I was asking a complex way of doing a simple thing. In CDI, to pass params around you cannot use @ManagedProperty, as explained above by BalusC. Instead, you just setup your xhtml files as so:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns:ui="http://java.sun.com/jsf/facelets"
                template="./template.xhtml"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:c="http://java.sun.com/jsp/jstl/core"
                xmlns:f="http://java.sun.com/jsf/core">
    <ui:define name="top">
        <h:form>
            <h:commandButton action="#{messages.back()}" value="..back" />
        </h:form>
        <h:form>
            <h:commandButton action="#{messages.forward()}" value="forward.." />
        </h:form>
    </ui:define>
    <ui:define name="content">
        <h:dataTable value="#{messages.model}" var="m">
            <h:column>
                <f:facet name="id">
                    <h:outputText value="id" />
                </f:facet>
                <h:outputLink id="hmmm" value="detail.xhtml">
                    <f:param name="id" value="#{m.getMessageNumber()}" />
                    <h:outputText value="#{m.getMessageNumber()}" />
                </h:outputLink>
            </h:column>
            <h:column>
                <f:facet name="subject">
                    <h:outputText value="subject" />
                </f:facet>
                <h:outputText value="#{m.subject}"></h:outputText>
            </h:column>
            <h:column>
                <f:facet name="content">
                    <h:outputText value="content" />
                </f:facet>
                <h:outputText value="#{m.sentDate}"></h:outputText>
            </h:column>
            <h:column>
                <f:facet name="date">
                    <h:outputText value="date" />
                </f:facet>
                <h:outputLink value="#{messages.getUrl(m)}">#{messages.getUrl(m)}</h:outputLink>
            </h:column>
        </h:dataTable>
    </ui:define>
</ui:composition>

to:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns:ui="http://java.sun.com/jsf/facelets"
                template="./template.xhtml"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:c="http://java.sun.com/jsp/jstl/core"
                xmlns:f="http://java.sun.com/jsf/core">
    <ui:define name="top">
        <h:form>
            <h:outputLink id="back" value="detail.xhtml">
                <f:metadata>
                    <f:viewParam name="id" value="#{detail.id}"  />
                </f:metadata>
                <f:param name="id" value="#{detail.back()}" />
                <h:outputText value="back" />
            </h:outputLink>
        </h:form>
        <h:form>
            <h:outputLink id="forward" value="detail.xhtml">
                <f:metadata>
                    <f:viewParam name="id" value="#{detail.id}"  />
                </f:metadata>
                <f:param name="id" value="#{detail.forward()}" />
                <h:outputText value="forward" />
            </h:outputLink>
        </h:form>
    </ui:define>
    <ui:define name="content">
        <h:outputText value="#{detail.content}"></h:outputText>
    </ui:define>
</ui:composition>

I'm only including this for anyone who comes along, to clarify that, for this simple example, you don't need a Converter, that the default works fine.

The original question is more than a bit mangled, as well. From looking at other questions on this, I think others could benefit from a simple example such as this. So many examples are overly complex, or involve EJB, etc.

Transpadane answered 10/4, 2012 at 11:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.